Skip to content
This repository has been archived by the owner on May 26, 2024. It is now read-only.

Commit

Permalink
Live activity fixes (Artificial-Pancreas#397)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
10nas authored Dec 1, 2023
1 parent 79f1237 commit fab1cbd
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 58 deletions.
2 changes: 1 addition & 1 deletion FreeAPS/Sources/Models/FreeAPSSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
93 changes: 58 additions & 35 deletions FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ 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
if mmol {
formatter.minimumFractionDigits = 1
formatter.maximumFractionDigits = 1
}
if forceSign {
formatter.positivePrefix = formatter.plusSign
}
formatter.roundingMode = .halfUp

return formatter
Expand All @@ -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 {
Expand All @@ -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 })
Expand All @@ -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(
Expand Down
40 changes: 21 additions & 19 deletions LiveActivity/LiveActivity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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("--")
}
Expand All @@ -26,15 +26,15 @@ struct LiveActivity: Widget {
if context.isStale {
Text("--")
} else {
Text("\(context.state.bg)")
Text(context.state.bg)
}
}

@ViewBuilder func bgAndTrend(context: ActivityViewContext<LiveActivityAttributes>) -> some View {
if context.isStale {
Text("--")
} else {
Text("\(context.state.bg)")
Text(context.state.bg)
if let trendSystemImage = context.state.trendSystemImage {
Image(systemName: trendSystemImage)
}
Expand All @@ -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 {
Expand All @@ -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()
}
Expand All @@ -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())
}
}

Expand Down

0 comments on commit fab1cbd

Please sign in to comment.