瀏覽代碼

feat: update watch os Live Activity

bastiaanv 6 月之前
父節點
當前提交
4a0652338f

+ 55 - 0
LiveActivity/LiveActivity+Helper.swift

@@ -179,6 +179,7 @@ func bgAndTrend(
             case .minimal:
                 let scaledText = text.scaleEffect(x: 0.7, y: 0.7, anchor: .leading)
                 scaledText.foregroundStyle(hasStaticColorScheme ? .primary : glucoseColor)
+
             case .compact:
                 text.scaleEffect(x: 0.8, y: 0.8, anchor: .leading).padding(.trailing, -3)
 
@@ -191,3 +192,57 @@ func bgAndTrend(
 
     return (stack, characters)
 }
+
+private struct LiveActivityWatchOS: EnvironmentKey {
+    // Value to add support for older iOS version (17 and lower) in order to keep using the ActivityFamily class
+    static let defaultValue = false
+}
+
+public extension EnvironmentValues {
+    var isWatchOS: Bool {
+        get { self[LiveActivityWatchOS.self] }
+        set { self[LiveActivityWatchOS.self] = newValue }
+    }
+}
+
+@available(iOS 18, *) struct LiveActivityWatchOSModifier: ViewModifier {
+    @Environment(\.activityFamily) var activityFamily
+
+    func body(content: Content) -> some View {
+        content.environment(\.isWatchOS, activityFamily == .small)
+    }
+}
+
+extension View {
+    @ViewBuilder func addIsWatchOS() -> some View {
+        if #available(iOS 18, *) {
+            modifier(LiveActivityWatchOSModifier())
+        } else {
+            self
+        }
+    }
+
+    @ViewBuilder func addLiveActivityModifiers(isWatchOS: Bool) -> some View {
+        modifier(LiveActivityModifiers(isWatchOS: isWatchOS))
+    }
+}
+
+struct LiveActivityModifiers: ViewModifier {
+    let isWatchOS: Bool
+
+    func body(content: Content) -> some View {
+        content
+            .padding(.all, isWatchOS ? 8 : 14)
+            .frame(minHeight: 0, maxHeight: .infinity)
+            .privacySensitive()
+            .foregroundStyle(Color.primary)
+            // Semantic BackgroundStyle and Color values work here. They adapt to the given interface style (light mode, dark
+            // mode)
+            // Semantic UIColors do NOT (as of iOS 17.1.1). Like UIColor.systemBackgroundColor (it does not adapt to changes of
+            // the interface style)
+            // The colorScheme environment variable does work here, but BackgroundStyle gives us this functionality for free
+            .foregroundStyle(Color.primary)
+            .background(BackgroundStyle.background.opacity(isWatchOS ? 1 : 0.4))
+            .activityBackgroundTint(isWatchOS ? .black : Color.clear)
+    }
+}

+ 101 - 35
LiveActivity/LiveActivity.swift

@@ -4,8 +4,9 @@ import WidgetKit
 
 struct LiveActivity: Widget {
     var body: some WidgetConfiguration {
-        ActivityConfiguration(for: LiveActivityAttributes.self) { context in
+        let configuration = ActivityConfiguration(for: LiveActivityAttributes.self) { context in
             LiveActivityView(context: context)
+                .addIsWatchOS()
         } dynamicIsland: { context in
             let hasStaticColorScheme = context.state.glucoseColorScheme == "staticColor"
 
@@ -19,8 +20,10 @@ struct LiveActivity: Widget {
 
                 return Color.getDynamicGlucoseColor(
                     glucoseValue: Decimal(string: state.bg) ?? 100,
-                    highGlucoseColorValue: !hasStaticColorScheme ? hardCodedHigh : state.highGlucose,
-                    lowGlucoseColorValue: !hasStaticColorScheme ? hardCodedLow : state.lowGlucose,
+                    highGlucoseColorValue: !hasStaticColorScheme
+                        ? hardCodedHigh : state.highGlucose,
+                    lowGlucoseColorValue: !hasStaticColorScheme
+                        ? hardCodedLow : state.lowGlucose,
                     targetGlucose: isMgdL ? state.target : state.target.asMmolL,
                     glucoseColorScheme: state.glucoseColorScheme
                 )
@@ -28,7 +31,10 @@ struct LiveActivity: Widget {
 
             return DynamicIsland {
                 DynamicIslandExpandedRegion(.leading) {
-                    LiveActivityExpandedLeadingView(context: context, glucoseColor: glucoseColor)
+                    LiveActivityExpandedLeadingView(
+                        context: context,
+                        glucoseColor: glucoseColor
+                    )
                 }
                 DynamicIslandExpandedRegion(.trailing) {
                     LiveActivityExpandedTrailingView(
@@ -43,11 +49,20 @@ struct LiveActivity: Widget {
                     LiveActivityExpandedCenterView(context: context)
                 }
             } compactLeading: {
-                LiveActivityCompactLeadingView(context: context, glucoseColor: glucoseColor)
+                LiveActivityCompactLeadingView(
+                    context: context,
+                    glucoseColor: glucoseColor
+                )
             } compactTrailing: {
-                LiveActivityCompactTrailingView(context: context, glucoseColor: hasStaticColorScheme ? .primary : glucoseColor)
+                LiveActivityCompactTrailingView(
+                    context: context,
+                    glucoseColor: hasStaticColorScheme ? .primary : glucoseColor
+                )
             } minimal: {
-                LiveActivityMinimalView(context: context, glucoseColor: glucoseColor)
+                LiveActivityMinimalView(
+                    context: context,
+                    glucoseColor: glucoseColor
+                )
             }
             .widgetURL(URL(string: "Trio://"))
             .keylineTint(glucoseColor)
@@ -55,6 +70,12 @@ struct LiveActivity: Widget {
             .contentMargins(.trailing, 0, for: .compactLeading)
             .contentMargins(.leading, 0, for: .compactTrailing)
         }
+
+        if #available(iOS 18.0, *) {
+            return configuration.supplementalActivityFamilies([.small])
+        } else {
+            return configuration
+        }
     }
 }
 
@@ -73,25 +94,35 @@ private extension LiveActivityAttributes {
 
 private extension LiveActivityAttributes.ContentState {
     static var chartData: [MockGlucoseData] = [
-        MockGlucoseData(glucose: 120, date: Date().addingTimeInterval(-600), direction: "flat"),
-        MockGlucoseData(glucose: 125, date: Date().addingTimeInterval(-300), direction: "flat"),
+        MockGlucoseData(
+            glucose: 120,
+            date: Date().addingTimeInterval(-600),
+            direction: "flat"
+        ),
+        MockGlucoseData(
+            glucose: 125,
+            date: Date().addingTimeInterval(-300),
+            direction: "flat"
+        ),
         MockGlucoseData(glucose: 130, date: Date(), direction: "flat")
     ]
 
-    static var detailedViewState = LiveActivityAttributes.ContentAdditionalState(
-        chart: chartData.map { Decimal($0.glucose) },
-        chartDate: chartData.map(\.date),
-        rotationDegrees: 0,
-        cob: 20,
-        iob: 1.5,
-        tdd: 43.21,
-        isOverrideActive: false,
-        overrideName: "Exercise",
-        overrideDate: Date().addingTimeInterval(-3600),
-        overrideDuration: 120,
-        overrideTarget: 150,
-        widgetItems: LiveActivityAttributes.LiveActivityItem.defaultItems
-    )
+    static var detailedViewState =
+        LiveActivityAttributes.ContentAdditionalState(
+            chart: chartData.map {
+                LiveActivityAttributes.ChartItem(value: Decimal($0.glucose), date: $0.date)
+            },
+            rotationDegrees: 0,
+            cob: 20,
+            iob: 1.5,
+            tdd: 43.21,
+            isOverrideActive: false,
+            overrideName: "Exercise",
+            overrideDate: Date().addingTimeInterval(-3600),
+            overrideDuration: 120,
+            overrideTarget: 150,
+            widgetItems: LiveActivityAttributes.LiveActivityItem.defaultItems
+        )
 
     // 0 is the widest digit. Use this to get an upper bound on text width.
 
@@ -107,7 +138,9 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
-            detailedViewState: nil,
+            useDetailedViewIOS: false,
+            useDetailedViewWatchOS: false,
+            detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
@@ -123,7 +156,9 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
-            detailedViewState: nil,
+            useDetailedViewIOS: false,
+            useDetailedViewWatchOS: false,
+            detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
@@ -139,7 +174,9 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
-            detailedViewState: nil,
+            useDetailedViewIOS: false,
+            useDetailedViewWatchOS: false,
+            detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
@@ -156,7 +193,9 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
-            detailedViewState: nil,
+            useDetailedViewIOS: false,
+            useDetailedViewWatchOS: false,
+            detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
@@ -172,7 +211,9 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
-            detailedViewState: nil,
+            useDetailedViewIOS: false,
+            useDetailedViewWatchOS: false,
+            detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
@@ -188,12 +229,15 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
-            detailedViewState: nil,
+            useDetailedViewIOS: false,
+            useDetailedViewWatchOS: false,
+            detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
 
-    static var testWideDetailed: LiveActivityAttributes.ContentState {
+    static var testWideDetailed: LiveActivityAttributes.ContentState
+    {
         LiveActivityAttributes.ContentState(
             unit: "mg/dL",
             bg: "00.0",
@@ -204,12 +248,16 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
+            useDetailedViewIOS: true,
+            useDetailedViewWatchOS: true,
             detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
 
-    static var testVeryWideDetailed: LiveActivityAttributes.ContentState {
+    static var testVeryWideDetailed:
+        LiveActivityAttributes.ContentState
+    {
         LiveActivityAttributes.ContentState(
             unit: "mg/dL",
             bg: "00.0",
@@ -220,12 +268,16 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
+            useDetailedViewIOS: true,
+            useDetailedViewWatchOS: true,
             detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
 
-    static var testSuperWideDetailed: LiveActivityAttributes.ContentState {
+    static var testSuperWideDetailed:
+        LiveActivityAttributes.ContentState
+    {
         LiveActivityAttributes.ContentState(
             unit: "mg/dL",
             bg: "00.0",
@@ -236,13 +288,17 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
+            useDetailedViewIOS: true,
+            useDetailedViewWatchOS: true,
             detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
 
     // 2 characters for BG, 1 character for change is the minimum that will be shown
-    static var testNarrowDetailed: LiveActivityAttributes.ContentState {
+    static var testNarrowDetailed:
+        LiveActivityAttributes.ContentState
+    {
         LiveActivityAttributes.ContentState(
             unit: "mg/dL",
             bg: "00",
@@ -253,12 +309,16 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
+            useDetailedViewIOS: true,
+            useDetailedViewWatchOS: true,
             detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
 
-    static var testMediumDetailed: LiveActivityAttributes.ContentState {
+    static var testMediumDetailed:
+        LiveActivityAttributes.ContentState
+    {
         LiveActivityAttributes.ContentState(
             unit: "mg/dL",
             bg: "000",
@@ -269,12 +329,16 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
+            useDetailedViewIOS: true,
+            useDetailedViewWatchOS: true,
             detailedViewState: detailedViewState,
             isInitialState: false
         )
     }
 
-    static var testExpiredDetailed: LiveActivityAttributes.ContentState {
+    static var testExpiredDetailed:
+        LiveActivityAttributes.ContentState
+    {
         LiveActivityAttributes.ContentState(
             unit: "mg/dL",
             bg: "--",
@@ -285,6 +349,8 @@ private extension LiveActivityAttributes.ContentState {
             lowGlucose: 70,
             target: 100,
             glucoseColorScheme: "staticColor",
+            useDetailedViewIOS: true,
+            useDetailedViewWatchOS: true,
             detailedViewState: detailedViewState,
             isInitialState: false
         )

+ 25 - 18
LiveActivity/Views/LiveActivityChartView.swift

@@ -11,6 +11,7 @@ import WidgetKit
 
 struct LiveActivityChartView: View {
     @Environment(\.colorScheme) var colorScheme
+    @Environment(\.isWatchOS) var isWatchOS
 
     var context: ActivityViewContext<LiveActivityAttributes>
     var additionalState: LiveActivityAttributes.ContentAdditionalState
@@ -19,9 +20,11 @@ struct LiveActivityChartView: View {
         let state = context.state
         let isMgdL: Bool = state.unit == "mg/dL"
 
+        let maxThreshhold: Decimal = isWatchOS ? 220 : 300
+
         // Determine scale
-        let minValue = min(additionalState.chart.min() ?? 39, 39) as Decimal
-        let maxValue = max(additionalState.chart.max() ?? 300, 300) as Decimal
+        let minValue = min(additionalState.chart.min(by: { $0.value < $1.value })?.value ?? 39, 39)
+        let maxValue = max(additionalState.chart.max(by: { $0.value < $1.value })?.value ?? maxThreshhold, maxThreshhold)
 
         let yAxisRuleMarkMin = isMgdL ? state.lowGlucose : state.lowGlucose
             .asMmolL
@@ -34,8 +37,9 @@ struct LiveActivityChartView: View {
         let calendar = Calendar.current
         let now = Date()
 
-        let startDate = calendar.date(byAdding: .hour, value: -6, to: now) ?? now
-        let endDate = isOverrideActive ? (calendar.date(byAdding: .hour, value: 2, to: now) ?? now) : now
+        let startDate = calendar.date(byAdding: .hour, value: isWatchOS ? -3 : -6, to: now) ?? now
+        let endDate = isOverrideActive ? (calendar.date(byAdding: .hour, value: 2, to: now) ?? now) :
+            (calendar.date(byAdding: .minute, value: isWatchOS ? 5 : 0, to: now) ?? now)
 
         // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
         let hardCodedLow = isMgdL ? Decimal(55) : 55.asMmolL
@@ -104,13 +108,13 @@ struct LiveActivityChartView: View {
     }
 
     private func drawActiveOverrides() -> some ChartContent {
-        let start: Date = context.state.detailedViewState?.overrideDate ?? .distantPast
+        let start: Date = context.state.detailedViewState.overrideDate
 
-        let duration = context.state.detailedViewState?.overrideDuration ?? 0
+        let duration = context.state.detailedViewState.overrideDuration
         let durationAsTimeInterval = TimeInterval((duration as NSDecimalNumber).doubleValue * 60) // return seconds
 
         let end: Date = start.addingTimeInterval(durationAsTimeInterval)
-        let target = context.state.detailedViewState?.overrideTarget ?? 0
+        let target = context.state.detailedViewState.overrideTarget
 
         return RuleMark(
             xStart: .value("Start", start, unit: .second),
@@ -122,19 +126,22 @@ struct LiveActivityChartView: View {
     }
 
     private func drawChart(yAxisRuleMarkMin _: Decimal, yAxisRuleMarkMax _: Decimal) -> some ChartContent {
-        ForEach(additionalState.chart.indices, id: \.self) { index in
-            let isMgdL = context.state.unit == "mg/dL"
-            let currentValue = additionalState.chart[index]
-            let displayValue = isMgdL ? currentValue : currentValue.asMmolL
-            let chartDate = additionalState.chartDate[index] ?? Date()
+        // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
+        let hardCodedLow = Decimal(55)
+        let hardCodedHigh = Decimal(220)
+        let hasStaticColorScheme = context.state.glucoseColorScheme == "staticColor"
+        let isMgdL = context.state.unit == "mg/dL"
+
+        let threeHours = TimeInterval(10800)
+        let chartData = isWatchOS ? additionalState.chart
+            .filter { abs($0.date.timeIntervalSinceNow) < threeHours } : additionalState
+            .chart
 
-            // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
-            let hardCodedLow = Decimal(55)
-            let hardCodedHigh = Decimal(220)
-            let hasStaticColorScheme = context.state.glucoseColorScheme == "staticColor"
+        return ForEach(chartData, id: \.self) { item in
+            let displayValue = isMgdL ? item.value : item.value.asMmolL
 
             let pointMarkColor = Color.getDynamicGlucoseColor(
-                glucoseValue: currentValue,
+                glucoseValue: item.value,
                 highGlucoseColorValue: !hasStaticColorScheme ? hardCodedHigh : context.state.highGlucose,
                 lowGlucoseColorValue: !hasStaticColorScheme ? hardCodedLow : context.state.lowGlucose,
                 targetGlucose: context.state.target,
@@ -142,7 +149,7 @@ struct LiveActivityChartView: View {
             )
 
             let pointMark = PointMark(
-                x: .value("Time", chartDate),
+                x: .value("Time", item.date),
                 y: .value("Value", displayValue)
             )
             .symbolSize(16)

+ 53 - 43
LiveActivity/Views/LiveActivityView.swift

@@ -1,9 +1,3 @@
-//
-//  LiveActivityView.swift
-//  Trio
-//
-//  Created by Cengiz Deniz on 17.10.24.
-//
 import ActivityKit
 import Foundation
 import SwiftUI
@@ -11,6 +5,8 @@ import WidgetKit
 
 struct LiveActivityView: View {
     @Environment(\.colorScheme) var colorScheme
+    @Environment(\.isWatchOS) var isWatchOS
+
     var context: ActivityViewContext<LiveActivityAttributes>
 
     private var hasStaticColorScheme: Bool {
@@ -35,15 +31,40 @@ struct LiveActivityView: View {
     }
 
     var body: some View {
-        if let detailedViewState = context.state.detailedViewState {
+        if isWatchOS, context.state.useDetailedViewWatchOS {
+            VStack {
+                LiveActivityBGLabelWatchView(context: context, glucoseColor: .primary)
+                LiveActivityChartView(context: context, additionalState: context.state.detailedViewState)
+                    .frame(maxWidth: UIScreen.main.bounds.width * 0.9)
+            }
+            .addLiveActivityModifiers(isWatchOS: true)
+
+        } else if isWatchOS {
+            HStack {
+                LiveActivityBGLabelLargeView(
+                    context: context,
+                    glucoseColor: glucoseColor
+                )
+                Spacer()
+                VStack {
+                    LiveActivityGlucoseDeltaLabelView(
+                        context: context,
+                        glucoseColor: .primary
+                    )
+                    LiveActivityUpdatedLabelView(context: context, isDetailedLayout: false, isWatchOS: true)
+                }
+            }
+            .addLiveActivityModifiers(isWatchOS: true)
+
+        } else if context.state.useDetailedViewIOS {
             VStack {
-                LiveActivityChartView(context: context, additionalState: detailedViewState)
+                LiveActivityChartView(context: context, additionalState: context.state.detailedViewState)
                     .frame(maxWidth: UIScreen.main.bounds.width * 0.9)
                     .frame(height: 80)
                     .overlay(alignment: .topTrailing) {
-                        if detailedViewState.isOverrideActive {
+                        if context.state.detailedViewState.isOverrideActive {
                             HStack {
-                                Text("\(detailedViewState.overrideName)")
+                                Text("\(context.state.detailedViewState.overrideName)")
                                     .font(.footnote)
                                     .fontWeight(.bold)
                                     .foregroundStyle(.white)
@@ -57,12 +78,15 @@ struct LiveActivityView: View {
                     }
 
                 HStack {
-                    if detailedViewState.widgetItems.contains(where: { $0 != .empty }) {
-                        ForEach(Array(detailedViewState.widgetItems.enumerated()), id: \.element) { index, widgetItem in
+                    if context.state.detailedViewState.widgetItems.contains(where: { $0 != .empty }) {
+                        ForEach(
+                            Array(context.state.detailedViewState.widgetItems.enumerated()),
+                            id: \.element
+                        ) { index, widgetItem in
                             switch widgetItem {
                             case .currentGlucose:
                                 VStack {
-                                    LiveActivityBGLabelView(context: context, additionalState: detailedViewState)
+                                    LiveActivityBGLabelView(context: context, additionalState: context.state.detailedViewState)
 
                                     HStack {
                                         LiveActivityGlucoseDeltaLabelView(
@@ -77,25 +101,24 @@ struct LiveActivityView: View {
                             case .currentGlucoseLarge:
                                 LiveActivityBGLabelLargeView(
                                     context: context,
-                                    additionalState: detailedViewState,
                                     glucoseColor: glucoseColor
                                 )
                             case .iob:
-                                LiveActivityIOBLabelView(context: context, additionalState: detailedViewState)
+                                LiveActivityIOBLabelView(context: context, additionalState: context.state.detailedViewState)
                             case .cob:
-                                LiveActivityCOBLabelView(context: context, additionalState: detailedViewState)
+                                LiveActivityCOBLabelView(context: context, additionalState: context.state.detailedViewState)
                             case .updatedLabel:
-                                LiveActivityUpdatedLabelView(context: context, isDetailedLayout: true)
+                                LiveActivityUpdatedLabelView(context: context, isDetailedLayout: true, isWatchOS: false)
                             case .totalDailyDose:
-                                LiveActivityTotalDailyDoseView(context: context, additionalState: detailedViewState)
+                                LiveActivityTotalDailyDoseView(context: context, additionalState: context.state.detailedViewState)
                             case .empty:
                                 Text("").frame(width: 50, height: 50)
                             }
 
                             /// Check if the next item is also non-empty to determine if a divider should be shown
-                            if index < detailedViewState.widgetItems.count - 1 {
-                                let currentItem = detailedViewState.widgetItems[index]
-                                let nextItem = detailedViewState.widgetItems[index + 1]
+                            if index < context.state.detailedViewState.widgetItems.count - 1 {
+                                let currentItem = context.state.detailedViewState.widgetItems[index]
+                                let nextItem = context.state.detailedViewState.widgetItems[index + 1]
 
                                 if currentItem != .empty, nextItem != .empty {
                                     Divider()
@@ -108,15 +131,7 @@ struct LiveActivityView: View {
                     }
                 }
             }
-            .privacySensitive()
-            .padding(.all, 14)
-            .foregroundStyle(Color.primary)
-            // Semantic BackgroundStyle and Color values work here. They adapt to the given interface style (light mode, dark mode)
-            // Semantic UIColors do NOT (as of iOS 17.1.1). Like UIColor.systemBackgroundColor (it does not adapt to changes of the interface style)
-            // The colorScheme environment variable does work here, but BackgroundStyle gives us this functionality for free
-            .foregroundStyle(Color.primary)
-            .background(BackgroundStyle.background.opacity(0.4))
-            .activityBackgroundTint(Color.clear)
+            .addLiveActivityModifiers(isWatchOS: false)
         } else {
             Group {
                 if context.state.isInitialState {
@@ -130,21 +145,14 @@ struct LiveActivityView: View {
                                 context: context,
                                 glucoseColor: hasStaticColorScheme ? .primary : glucoseColor
                             ).font(.title3)
-                            LiveActivityUpdatedLabelView(context: context, isDetailedLayout: false).font(.caption)
+                            LiveActivityUpdatedLabelView(context: context, isDetailedLayout: false, isWatchOS: false)
+                                .font(.caption)
                                 .foregroundStyle(.primary.opacity(0.7))
                         }
                     }
                 }
             }
-            .privacySensitive()
-            .padding(.all, 15)
-            .foregroundStyle(Color.primary)
-            /// Semantic BackgroundStyle and Color values work here. They adapt to the given interface style (light mode, dark mode)
-            // Semantic UIColors do NOT (as of iOS 17.1.1). Like UIColor.systemBackgroundColor (it does not adapt to changes of the interface style)
-            // The colorScheme environment variable does work here, but BackgroundStyle gives us this functionality for free
-            .foregroundStyle(Color.primary)
-            .background(BackgroundStyle.background.opacity(0.4))
-            .activityBackgroundTint(Color.clear)
+            .addLiveActivityModifiers(isWatchOS: false)
         }
     }
 }
@@ -176,8 +184,9 @@ struct LiveActivityExpandedBottomView: View {
     var body: some View {
         if context.state.isInitialState {
             Text("Live Activity Expired. Open Trio to Refresh").minimumScaleFactor(0.01)
-        } else if let detailedViewState = context.state.detailedViewState {
-            LiveActivityChartView(context: context, additionalState: detailedViewState)
+        } else if context.state.useDetailedViewIOS {
+            LiveActivityChartView(context: context, additionalState: context.state.detailedViewState)
+                .addIsWatchOS()
         }
     }
 }
@@ -186,8 +195,9 @@ struct LiveActivityExpandedCenterView: View {
     var context: ActivityViewContext<LiveActivityAttributes>
 
     var body: some View {
-        LiveActivityUpdatedLabelView(context: context, isDetailedLayout: false).font(Font.caption)
+        LiveActivityUpdatedLabelView(context: context, isDetailedLayout: false, isWatchOS: false).font(Font.caption)
             .foregroundStyle(Color.secondary)
+            .addIsWatchOS()
     }
 }
 

+ 4 - 4
LiveActivity/Views/WidgetItems/LiveActivityBGLabelLargeView.swift

@@ -4,26 +4,26 @@ import WidgetKit
 
 struct LiveActivityBGLabelLargeView: View {
     var context: ActivityViewContext<LiveActivityAttributes>
-    var additionalState: LiveActivityAttributes.ContentAdditionalState
     var glucoseColor: Color
+    var smallerFont: Bool = false
 
     var body: some View {
         HStack(alignment: .center) {
             if let trendArrow = context.state.direction {
                 Text(context.state.bg)
                     .fontWeight(.bold)
-                    .font(.title)
+                    .font(!smallerFont ? .title : .title3)
                     .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)
+                    .font(!smallerFont ? .headline : .subheadline)
             } else {
                 Text(context.state.bg)
                     .fontWeight(.bold)
-                    .font(.title)
+                    .font(!smallerFont ? .title : .title3)
                     .foregroundStyle(context.isStale ? .secondary : glucoseColor)
                     .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
             }

+ 0 - 6
LiveActivity/Views/WidgetItems/LiveActivityBGLabelView.swift

@@ -1,9 +1,3 @@
-//
-//  LiveActivityBGLabelView.swift
-//  Trio
-//
-//  Created by Cengiz Deniz on 17.10.24.
-//
 import Foundation
 import SwiftUI
 import WidgetKit

+ 45 - 0
LiveActivity/Views/WidgetItems/LiveActivityBGLabelWatchView.swift

@@ -0,0 +1,45 @@
+import SwiftUI
+import WidgetKit
+
+struct LiveActivityBGLabelWatchView: View {
+    var context: ActivityViewContext<LiveActivityAttributes>
+    var glucoseColor: Color
+
+    private var dateFormatter: DateFormatter {
+        let formatter = DateFormatter()
+        formatter.dateStyle = .none
+        formatter.timeStyle = .short
+        return formatter
+    }
+
+    var body: some View {
+        HStack {
+            Text(context.state.bg)
+                .fontWeight(.bold)
+                .font(.callout)
+                .foregroundStyle(context.isStale ? .secondary : glucoseColor)
+                .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+
+            if let trendArrow = context.state.direction {
+                Text(trendArrow)
+                    .fontWeight(.bold)
+                    .font(.callout)
+                    .foregroundStyle(context.isStale ? .secondary : glucoseColor)
+                    .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+            }
+
+            Text(context.state.change)
+                .font(.callout)
+                .foregroundStyle(.primary)
+                .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+
+            Spacer()
+
+            Text("\((context.state.date != nil) ? dateFormatter.string(from: context.state.date!) : "--")")
+                .font(.callout)
+                .bold()
+                .foregroundStyle(context.isStale ? .red.opacity(0.6) : .secondary)
+                .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+        }
+    }
+}

+ 5 - 10
LiveActivity/Views/WidgetItems/LiveActivityCOBLabelView.swift

@@ -1,9 +1,3 @@
-//
-//  LiveActivityCOBLabelView.swift
-//  Trio
-//
-//  Created by Cengiz Deniz on 17.10.24.
-//
 import Foundation
 import SwiftUI
 import WidgetKit
@@ -15,9 +9,8 @@ struct LiveActivityCOBLabelView: View {
     var body: some View {
         VStack(spacing: 2) {
             HStack {
-                Text(
-                    "\(additionalState.cob)"
-                ).fontWeight(.bold)
+                Text("\(additionalState.cob)" as String)
+                    .fontWeight(.bold)
                     .font(.title3)
                     .foregroundStyle(context.isStale ? .secondary : .primary)
                     .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
@@ -27,7 +20,9 @@ struct LiveActivityCOBLabelView: View {
                     .foregroundStyle(context.isStale ? .secondary : .primary)
                     .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
             }
-            Text("COB").font(.subheadline).foregroundStyle(.primary)
+            Text("COB")
+                .font(.subheadline)
+                .foregroundStyle(.primary)
         }
     }
 }

+ 8 - 14
LiveActivity/Views/WidgetItems/LiveActivityIOBLabelView.swift

@@ -1,9 +1,3 @@
-//
-//  LiveActivityWidgetItems.swift
-//  LiveActivityExtension
-//
-//  Created by Cengiz Deniz on 17.10.24.
-//
 import Foundation
 import SwiftUI
 import WidgetKit
@@ -22,20 +16,20 @@ struct LiveActivityIOBLabelView: View {
     var body: some View {
         VStack(spacing: 2) {
             HStack {
-                Text(
-                    bolusFormatter.string(from: additionalState.iob as NSNumber) ?? "--"
-                )
-                .fontWeight(.bold)
-                .font(.title3)
-                .foregroundStyle(context.isStale ? .secondary : .primary)
-                .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+                Text(bolusFormatter.string(from: additionalState.iob as NSNumber) ?? "--")
+                    .fontWeight(.bold)
+                    .font(.title3)
+                    .foregroundStyle(context.isStale ? .secondary : .primary)
+                    .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
 
                 Text(String(localized: "U", comment: "Insulin unit"))
                     .font(.headline).fontWeight(.bold)
                     .foregroundStyle(context.isStale ? .secondary : .primary)
                     .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
             }
-            Text("IOB").font(.subheadline).foregroundStyle(.primary)
+            Text("IOB")
+                .font(.subheadline)
+                .foregroundStyle(.primary)
         }
     }
 }

+ 8 - 8
LiveActivity/Views/WidgetItems/LiveActivityTotalDailyDoseView.swift

@@ -16,20 +16,20 @@ struct LiveActivityTotalDailyDoseView: View {
     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(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(String(localized: "U", comment: "Insulin unit"))
                     .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)
+            Text("TDD")
+                .font(.subheadline)
+                .foregroundStyle(.primary)
         }
     }
 }

+ 15 - 9
LiveActivity/Views/WidgetItems/LiveActivityUpdatedLabelView.swift

@@ -1,9 +1,3 @@
-//
-//  LiveActivityUpdatedLabelView.swift
-//  Trio
-//
-//  Created by Cengiz Deniz on 17.10.24.
-//
 import Foundation
 import SwiftUI
 import WidgetKit
@@ -11,6 +5,7 @@ import WidgetKit
 struct LiveActivityUpdatedLabelView: View {
     var context: ActivityViewContext<LiveActivityAttributes>
     var isDetailedLayout: Bool
+    var isWatchOS: Bool
 
     private var dateFormatter: DateFormatter {
         let formatter = DateFormatter()
@@ -22,7 +17,14 @@ struct LiveActivityUpdatedLabelView: View {
     var body: some View {
         let dateText = Text("\((context.state.date != nil) ? dateFormatter.string(from: context.state.date!) : "--")")
 
-        if isDetailedLayout {
+        if isWatchOS {
+            dateText
+                .font(.subheadline)
+                .bold()
+                .foregroundStyle(context.isStale ? .red.opacity(0.6) : .secondary)
+                .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+
+        } else if isDetailedLayout {
             VStack {
                 dateText
                     .font(.title3)
@@ -30,11 +32,15 @@ struct LiveActivityUpdatedLabelView: View {
                     .foregroundStyle(context.isStale ? .red.opacity(0.6) : .primary)
                     .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
 
-                Text("Updated").font(.subheadline).foregroundStyle(.primary)
+                Text("Updated")
+                    .font(.subheadline)
+                    .foregroundStyle(.primary)
             }
         } else {
             HStack {
-                Text("Updated:").font(.subheadline).foregroundStyle(.secondary)
+                Text("Updated:")
+                    .font(.subheadline)
+                    .foregroundStyle(.secondary)
 
                 dateText
                     .font(.subheadline)

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

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

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

@@ -8,10 +8,12 @@ extension LiveActivitySettings {
         @Published var units: GlucoseUnits = .mgdL
         @Published var useLiveActivity = false
         @Published var lockScreenView: LockScreenView = .simple
+        @Published var smartStackView: LockScreenView = .simple
         override func subscribe() {
             units = settingsManager.settings.units
             subscribeSetting(\.useLiveActivity, on: $useLiveActivity) { useLiveActivity = $0 }
             subscribeSetting(\.lockScreenView, on: $lockScreenView) { lockScreenView = $0 }
+            subscribeSetting(\.smartStackView, on: $smartStackView) { smartStackView = $0 }
         }
     }
 }

+ 67 - 5
Trio/Sources/Modules/LiveActivitySettings/View/LiveActivitySettingsRootView.swift

@@ -7,7 +7,8 @@ extension LiveActivitySettings {
         let resolver: Resolver
         @StateObject var state = StateModel()
 
-        @State private var shouldDisplayHint: Bool = false
+        @State private var shouldDisplayHintLockScreen: Bool = false
+        @State private var shouldDisplayHintSmartStack: Bool = false
         @State var hintDetent = PresentationDetent.large
         @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
@@ -48,7 +49,7 @@ extension LiveActivitySettings {
                     SettingInputSection(
                         decimalValue: $decimalPlaceholder,
                         booleanValue: $state.useLiveActivity,
-                        shouldDisplayHint: $shouldDisplayHint,
+                        shouldDisplayHint: $shouldDisplayHintLockScreen,
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
@@ -128,7 +129,7 @@ extension LiveActivitySettings {
                                                         }.font(.footnote)
                                                     }
                                                 )
-                                            shouldDisplayHint.toggle()
+                                            shouldDisplayHintLockScreen.toggle()
                                         },
                                         label: {
                                             HStack {
@@ -152,16 +153,77 @@ extension LiveActivitySettings {
                             }
                         }.listRowBackground(Color.chart)
                     }
+
+                    Section {
+                        VStack {
+                            Picker(
+                                selection: $state.smartStackView,
+                                label: Text("Watch Widget Style")
+                            ) {
+                                ForEach(LockScreenView.allCases) { selection in
+                                    Text(selection.displayName).tag(selection)
+                                }
+                            }.padding(.top)
+
+                            HStack(alignment: .center) {
+                                Text(
+                                    "Select simple or detailed style. See hint for more details."
+                                )
+                                .font(.footnote)
+                                .foregroundColor(.secondary)
+                                .lineLimit(nil)
+                                Spacer()
+                                Button(
+                                    action: {
+                                        hintLabel = String(localized: "Watch Widget Style")
+                                        selectedVerboseHint =
+                                            AnyView(
+                                                VStack(alignment: .leading, spacing: 10) {
+                                                    Text("Default: Simple").bold()
+                                                    VStack(alignment: .leading, spacing: 10) {
+                                                        Text("Simple:").bold()
+                                                        Text(
+                                                            "Trio's Simple Watch Widget displays current glucose reading, trend arrow, delta and the timestamp of the current reading."
+                                                        )
+                                                    }
+                                                    VStack(alignment: .leading, spacing: 10) {
+                                                        Text("Detailed:").bold()
+                                                        Text(
+                                                            "The Detailed Watch Screen Widget offers users a glucose chart as well as the current glucose, delta and the timestamp of current reading."
+                                                        )
+                                                    }
+                                                }
+                                            )
+                                        shouldDisplayHintSmartStack.toggle()
+                                    },
+                                    label: {
+                                        HStack {
+                                            Image(systemName: "questionmark.circle")
+                                        }
+                                    }
+                                ).buttonStyle(BorderlessButtonStyle())
+                            }.padding(.top)
+                        }.padding(.bottom)
+                    }.listRowBackground(Color.chart)
                 }
             }
             .listSectionSpacing(sectionSpacing)
             .onReceive(resolver.resolve(LiveActivityManager.self)!.$systemEnabled, perform: {
                 self.systemLiveActivitySetting = $0
             })
-            .sheet(isPresented: $shouldDisplayHint) {
+            .sheet(isPresented: $shouldDisplayHintLockScreen) {
+                SettingInputHintView(
+                    hintDetent: $hintDetent,
+                    shouldDisplayHint: $shouldDisplayHintLockScreen,
+                    hintLabel: hintLabel ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
+                    sheetTitle: String(localized: "Help", comment: "Help sheet title")
+                )
+            }
+            .sheet(isPresented: $shouldDisplayHintSmartStack) {
                 SettingInputHintView(
                     hintDetent: $hintDetent,
-                    shouldDisplayHint: $shouldDisplayHint,
+                    shouldDisplayHint: $shouldDisplayHintSmartStack,
                     hintLabel: hintLabel ?? "",
                     hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: String(localized: "Help", comment: "Help sheet title")

+ 9 - 3
Trio/Sources/Services/LiveActivity/LiveActitiyAttributes.swift

@@ -24,15 +24,16 @@ struct LiveActivityAttributes: ActivityAttributes {
         let lowGlucose: Decimal
         let target: Decimal
         let glucoseColorScheme: String
-        let detailedViewState: ContentAdditionalState?
+        let useDetailedViewIOS: Bool
+        let useDetailedViewWatchOS: Bool
+        let detailedViewState: ContentAdditionalState
 
         /// true for the first state that is set on the activity
         let isInitialState: Bool
     }
 
     struct ContentAdditionalState: Codable, Hashable {
-        let chart: [Decimal]
-        let chartDate: [Date?]
+        let chart: [ChartItem]
         let rotationDegrees: Double
         let cob: Decimal
         let iob: Decimal
@@ -45,5 +46,10 @@ struct LiveActivityAttributes: ActivityAttributes {
         let widgetItems: [LiveActivityItem]
     }
 
+    struct ChartItem: Codable, Hashable {
+        let value: Decimal
+        let date: Date
+    }
+
     let startDate: Date
 }

+ 16 - 27
Trio/Sources/Services/LiveActivity/LiveActivityAttributes+Helper.swift

@@ -68,7 +68,7 @@ extension LiveActivityAttributes.ContentState {
     ) {
         let glucose = bg.glucose
         let formattedBG = Self.formatGlucose(Int(glucose), units: units, forceSign: false)
-        var rotationDegrees: Double = 0.0
+        var rotationDegrees = 0.0
 
         switch bg.direction {
         case .doubleUp,
@@ -96,32 +96,19 @@ extension LiveActivityAttributes.ContentState {
         let trendString = bg.direction?.symbol as? String
         let change = Self.calculateChange(chart: chart, units: units)
 
-        let detailedState: LiveActivityAttributes.ContentAdditionalState?
-
-        switch settings.lockScreenView {
-        case .detailed:
-            let chartBG = chart.map { Decimal($0.glucose) }
-            let chartDate = chart.map(\.date)
-
-            /// glucose limits from UI settings, not from notifications settings
-            detailedState = LiveActivityAttributes.ContentAdditionalState(
-                chart: chartBG,
-                chartDate: chartDate,
-                rotationDegrees: rotationDegrees,
-                cob: Decimal(determination?.cob ?? 0),
-                iob: iob ?? 0 as Decimal,
-                tdd: determination?.tdd ?? 0 as Decimal,
-                isOverrideActive: override?.isActive ?? false,
-                overrideName: override?.overrideName ?? "Override",
-                overrideDate: override?.date ?? Date(),
-                overrideDuration: override?.duration ?? 0,
-                overrideTarget: override?.target ?? 0,
-                widgetItems: widgetItems ?? [] // set empty array here to silence compiler; this can never be nil
-            )
-
-        case .simple:
-            detailedState = nil
-        }
+        let detailedState = LiveActivityAttributes.ContentAdditionalState(
+            chart: chart.map { LiveActivityAttributes.ChartItem(value: Decimal($0.glucose), date: $0.date) },
+            rotationDegrees: rotationDegrees,
+            cob: Decimal(determination?.cob ?? 0),
+            iob: iob ?? 0 as Decimal,
+            tdd: determination?.tdd ?? 0 as Decimal,
+            isOverrideActive: override?.isActive ?? false,
+            overrideName: override?.overrideName ?? "Override",
+            overrideDate: override?.date ?? Date(),
+            overrideDuration: override?.duration ?? 0,
+            overrideTarget: override?.target ?? 0,
+            widgetItems: widgetItems ?? [] // set empty array here to silence compiler; this can never be nil
+        )
 
         self.init(
             unit: settings.units.rawValue,
@@ -133,6 +120,8 @@ extension LiveActivityAttributes.ContentState {
             lowGlucose: settings.low,
             target: determination?.target ?? 100 as Decimal,
             glucoseColorScheme: settings.glucoseColorScheme.rawValue,
+            useDetailedViewIOS: settings.lockScreenView == .detailed,
+            useDetailedViewWatchOS: settings.smartStackView == .detailed,
             detailedViewState: detailedState,
             isInitialState: false
         )

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

@@ -2,7 +2,7 @@ import ActivityKit
 import Foundation
 
 struct LiveActivityAttributes: ActivityAttributes {
-    public struct ContentState: Codable, Hashable {
+    struct ContentState: Codable, Hashable {
         let bg: String
         let direction: String?
         let change: String

+ 21 - 7
Trio/Sources/Services/LiveActivity/LiveActivityManager.swift

@@ -10,7 +10,8 @@ import UIKit
 
     /// Determines if the current activity needs to be recreated.
     ///
-    /// - Returns: `true` if the activity is dismissed, ended, stale, or has been active for more than 60 minutes; otherwise, `false`.
+    /// - Returns: `true` if the activity is dismissed, ended, stale, or has been active for more than 60 minutes; otherwise,
+    /// `false`.
     func needsRecreation() -> Bool {
         switch activity.activityState {
         case .dismissed,
@@ -47,8 +48,7 @@ final class LiveActivityData: ObservableObject {
 ///
 /// Additionally, it supports a restart functionality (via `restartActivityFromLiveActivityIntent()`)
 /// via iOS shortcuts, similar to other iOS apps like xDrip4iOS or Sweet Dreams.
-@available(iOS 16.2, *)
-final class LiveActivityManager: Injectable, ObservableObject, SettingsObserver {
+@available(iOS 16.2, *) final class LiveActivityManager: Injectable, ObservableObject, SettingsObserver {
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var storage: FileStorage!
@@ -288,7 +288,21 @@ final class LiveActivityManager: Injectable, ObservableObject, SettingsObserver
                             lowGlucose: settings.low,
                             target: data.determination?.target ?? 100 as Decimal,
                             glucoseColorScheme: settings.glucoseColorScheme.rawValue,
-                            detailedViewState: nil,
+                            useDetailedViewIOS: false,
+                            useDetailedViewWatchOS: false,
+                            detailedViewState: LiveActivityAttributes.ContentAdditionalState(
+                                chart: [],
+                                rotationDegrees: 0,
+                                cob: 0,
+                                iob: 0,
+                                tdd: 0,
+                                isOverrideActive: false,
+                                overrideName: "",
+                                overrideDate: Date.now,
+                                overrideDuration: 0,
+                                overrideTarget: 0,
+                                widgetItems: []
+                            ),
                             isInitialState: true
                         ),
                     staleDate: Date.now.addingTimeInterval(60)
@@ -340,7 +354,8 @@ final class LiveActivityManager: Injectable, ObservableObject, SettingsObserver
 
     /// Restarts the live activity from a Live Activity Intent.
     ///
-    /// This method mimics xdrip's `restartActivityFromLiveActivityIntent()` behavior by verifying that a valid content state exists,
+    /// This method mimics xdrip's `restartActivityFromLiveActivityIntent()` behavior by verifying that a valid content state
+    /// exists,
     /// ending the current live activity, and starting a new one using the current state.
     @MainActor func restartActivityFromLiveActivityIntent() async {
         await endActivity()
@@ -362,8 +377,7 @@ final class LiveActivityManager: Injectable, ObservableObject, SettingsObserver
     }
 }
 
-@available(iOS 16.2, *)
-extension LiveActivityManager {
+@available(iOS 16.2, *) extension LiveActivityManager {
     @MainActor func pushCurrentContent() async {
         guard let glucose = data.glucoseFromPersistence, let bg = glucose.first else {
             debug(.default, "[LiveActivityManager] pushCurrentContent: no current glucose data available")