Quellcode durchsuchen

Live activity fixes (#397)

* fix mmol live activity, live activity off by default, re-create live activity always after it ended, better readability on lock screen

* typo

* lock screen cleanup

* fix padding, fix misaligned trailing dynamic island view

* camel case

* prevent unwanted hiding of live activity, update dynamic island padding

* also hide change label on stale live activity data

* update colors
10nas vor 2 Jahren
Ursprung
Commit
fab1cbda3c

+ 1 - 1
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -50,7 +50,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var fattyMeals: Bool = false
     var fattyMealFactor: Decimal = 0.7
     var displayPredictions: Bool = true
-    var useLiveActivity: Bool = true
+    var useLiveActivity: Bool = false
 }
 
 extension FreeAPSSettings: Decodable {

+ 1 - 1
FreeAPS/Sources/Modules/NotificationsConfig/NotificationsConfigStateModel.swift

@@ -9,7 +9,7 @@ extension NotificationsConfig {
         @Published var lowGlucose: Decimal = 0
         @Published var highGlucose: Decimal = 0
         @Published var carbsRequiredThreshold: Decimal = 0
-        @Published var useLiveActivity = true
+        @Published var useLiveActivity = false
         var units: GlucoseUnits = .mmolL
 
         override func subscribe() {

+ 1 - 1
FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift

@@ -60,7 +60,7 @@ extension NotificationsConfig {
                     Section(
                         header: Text("Live Activity"),
                         footer: Text(
-                            "Live activity displays blood gluocse live on the lock screen and on the dynamic island (if available)"
+                            "Live activity displays blood glucose live on the lock screen and on the dynamic island (if available)"
                         )
                     ) {
                         Toggle("Show live activity", isOn: $state.useLiveActivity)

+ 1 - 1
FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift

@@ -5,7 +5,7 @@ struct LiveActivityAttributes: ActivityAttributes {
     public struct ContentState: Codable, Hashable {
         let bg: String
         let trendSystemImage: String?
-        let change: Int?
+        let change: String
         let date: Date
     }
 

+ 58 - 35
FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift

@@ -4,7 +4,7 @@ import Swinject
 import UIKit
 
 extension LiveActivityAttributes.ContentState {
-    static func formatGlucose(_ value: Int, mmol: Bool) -> String {
+    static func formatGlucose(_ value: Int, mmol: Bool, forceSign: Bool) -> String {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal
         formatter.maximumFractionDigits = 0
@@ -12,6 +12,9 @@ extension LiveActivityAttributes.ContentState {
             formatter.minimumFractionDigits = 1
             formatter.maximumFractionDigits = 1
         }
+        if forceSign {
+            formatter.positivePrefix = formatter.plusSign
+        }
         formatter.roundingMode = .halfUp
 
         return formatter
@@ -25,45 +28,62 @@ extension LiveActivityAttributes.ContentState {
             return nil
         }
 
-        let formattedBG = Self.formatGlucose(glucose, mmol: mmol)
+        let formattedBG = Self.formatGlucose(glucose, mmol: mmol, forceSign: false)
 
-        let trentString: String?
+        let trendString: String?
         switch bg.direction {
         case .doubleUp,
              .singleUp,
              .tripleUp:
-            trentString = "arrow.up"
+            trendString = "arrow.up"
 
         case .fortyFiveUp:
-            trentString = "arrow.up.right"
+            trendString = "arrow.up.right"
 
         case .flat:
-            trentString = "arrow.right"
+            trendString = "arrow.right"
 
         case .fortyFiveDown:
-            trentString = "arrow.down.right"
+            trendString = "arrow.down.right"
 
         case .doubleDown,
              .singleDown,
              .tripleDown:
-            trentString = "arrow.down"
+            trendString = "arrow.down"
 
         case .notComputable,
              Optional.none,
              .rateOutOfRange,
              .some(.none):
-            trentString = nil
+            trendString = nil
         }
 
-        let change = prev?.glucose.map({ glucose - $0 })
+        let change = prev?.glucose.map({
+            Self.formatGlucose(glucose - $0, mmol: mmol, forceSign: true)
+        }) ?? ""
 
-        self.init(bg: formattedBG, trendSystemImage: trentString, change: change, date: bg.dateString)
+        self.init(bg: formattedBG, trendSystemImage: trendString, change: change, date: bg.dateString)
     }
 }
 
 @available(iOS 16.2, *) private struct ActiveActivity {
     let activity: Activity<LiveActivityAttributes>
     let startDate: Date
+
+    func needsRecreation() -> Bool {
+        switch activity.activityState {
+        case .dismissed,
+             .ended:
+            return true
+        case .active,
+             .stale: break
+        @unknown default:
+            return true
+        }
+
+        return -startDate.timeIntervalSinceNow >
+            TimeInterval(60 * 60)
+    }
 }
 
 @available(iOS 16.2, *) final class LiveActivityBridge: Injectable {
@@ -87,29 +107,37 @@ extension LiveActivityAttributes.ContentState {
             object: nil,
             queue: nil
         ) { _ in
-            // just before app resigns active, show a new activity
-            // only do this if there is no current activity or the current activity is older than 1h
-            if self.settings.useLiveActivity {
-                if (self.currentActivity?.startDate).map({ -$0.timeIntervalSinceNow >
-                        TimeInterval(60 * 60) }) ?? true
-                {
-                    self.forceActivityUpdate()
-                }
-            } else {
-                Task {
-                    await self.endActivity()
-                }
-            }
+            self.forceActivityUpdate()
+        }
+
+        Foundation.NotificationCenter.default.addObserver(
+            forName: UIApplication.didBecomeActiveNotification,
+            object: nil,
+            queue: nil
+        ) { _ in
+            self.forceActivityUpdate()
         }
     }
 
-    /// creates and tries to present a new activity update from the current GlucoseStorage values
+    /// creates and tries to present a new activity update from the current GlucoseStorage values if live activities are enabled in settings
+    /// Ends existing live activities if live activities are not enabled in settings
     private func forceActivityUpdate() {
-        glucoseDidUpdate(glucoseStorage.recent())
+        // just before app resigns active, show a new activity
+        // only do this if there is no current activity or the current activity is older than 1h
+        if settings.useLiveActivity {
+            if currentActivity?.needsRecreation() ?? true
+            {
+                glucoseDidUpdate(glucoseStorage.recent())
+            }
+        } else {
+            Task {
+                await self.endActivity()
+            }
+        }
     }
 
     /// attempts to present this live activity state, creating a new activity if none exists yet
-    private func pushUpdate(_ state: LiveActivityAttributes.ContentState) async {
+    @MainActor private func pushUpdate(_ state: LiveActivityAttributes.ContentState) async {
         // hide duplicate/unknown activities
         for unknownActivity in Activity<LiveActivityAttributes>.activities
             .filter({ self.currentActivity?.activity.id != $0.id })
@@ -120,18 +148,13 @@ extension LiveActivityAttributes.ContentState {
         let content = ActivityContent(state: state, staleDate: state.date.addingTimeInterval(TimeInterval(6 * 60)))
 
         if let currentActivity {
-            switch currentActivity.activity.activityState {
-            case .dismissed,
-                 .ended:
-                // activity is no longer visible. End it and try to push the update again
+            if currentActivity.needsRecreation(), UIApplication.shared.applicationState == .active {
+                // activity is no longer visible or old. End it and try to push the update again
                 await endActivity()
                 await pushUpdate(state)
-            case .active,
-                 .stale: await currentActivity.activity.update(content)
-            @unknown default:
+            } else {
                 await currentActivity.activity.update(content)
             }
-
         } else {
             do {
                 let activity = try Activity.request(

+ 21 - 19
LiveActivity/LiveActivity.swift

@@ -11,8 +11,8 @@ struct LiveActivity: Widget {
     }()
 
     func changeLabel(context: ActivityViewContext<LiveActivityAttributes>) -> Text {
-        if let change = context.state.change {
-            Text("\(change > 0 ? "+" : "")\(change)")
+        if !context.isStale && !context.state.change.isEmpty {
+            Text(context.state.change)
         } else {
             Text("--")
         }
@@ -26,7 +26,7 @@ struct LiveActivity: Widget {
         if context.isStale {
             Text("--")
         } else {
-            Text("\(context.state.bg)")
+            Text(context.state.bg)
         }
     }
 
@@ -34,7 +34,7 @@ struct LiveActivity: Widget {
         if context.isStale {
             Text("--")
         } else {
-            Text("\(context.state.bg)")
+            Text(context.state.bg)
             if let trendSystemImage = context.state.trendSystemImage {
                 Image(systemName: trendSystemImage)
             }
@@ -44,21 +44,22 @@ struct LiveActivity: Widget {
     var body: some WidgetConfiguration {
         ActivityConfiguration(for: LiveActivityAttributes.self) { context in
             // Lock screen/banner UI goes here
-            VStack(alignment: .trailing, spacing: 0) {
-                HStack(alignment: .top) {
-                    HStack(alignment: .center, spacing: 3) {
-                        bgAndTrend(context: context).font(.title)
-                    }
-                    Spacer()
+
+            HStack(spacing: 3) {
+                bgAndTrend(context: context).font(.title)
+                Spacer()
+                VStack(alignment: .trailing, spacing: 5) {
                     changeLabel(context: context).font(.title3)
+                    updatedLabel(context: context).font(.caption).foregroundStyle(.black.opacity(0.7))
                 }
-                .imageScale(.small)
-                updatedLabel(context: context).font(.caption).offset(y: -3)
             }
-            .padding(.horizontal, 20)
-            .padding(.vertical, 10)
+            .privacySensitive()
+            .imageScale(.small)
+            .padding(.all, 15)
+            .background(Color.white.opacity(0.2))
+            .foregroundColor(Color.black)
             .activityBackgroundTint(Color.cyan.opacity(0.2))
-            .activitySystemActionForegroundColor(Color.white)
+            .activitySystemActionForegroundColor(Color.black)
 
         } dynamicIsland: { context in
             DynamicIsland {
@@ -73,14 +74,15 @@ struct LiveActivity: Widget {
                     changeLabel(context: context).font(.title).padding(.trailing, 5)
                 }
                 DynamicIslandExpandedRegion(.bottom) {
-                    updatedLabel(context: context).font(.caption)
+                    updatedLabel(context: context).font(.caption).foregroundStyle(Color.secondary)
+                        .padding(.bottom, 5)
                 }
             } compactLeading: {
                 HStack(spacing: 1) {
                     bgAndTrend(context: context)
-                }.bold().imageScale(.small)
+                }.bold().imageScale(.small).padding(.leading, 5)
             } compactTrailing: {
-                changeLabel(context: context)
+                changeLabel(context: context).padding(.trailing, 5)
             } minimal: {
                 bgLabel(context: context).bold()
             }
@@ -98,7 +100,7 @@ private extension LiveActivityAttributes {
 
 private extension LiveActivityAttributes.ContentState {
     static var test: LiveActivityAttributes.ContentState {
-        LiveActivityAttributes.ContentState(bg: "100", trendSystemImage: "arrow.right", change: 2, date: Date())
+        LiveActivityAttributes.ContentState(bg: "100", trendSystemImage: "arrow.right", change: "+2", date: Date())
     }
 }