63 lines
2.5 KiB
Swift
63 lines
2.5 KiB
Swift
import ActivityKit
|
|||
|
|
import Foundation
|
||
|
|
import NucleicProtocol
|
||
|
|
|
||
|
|
/// Owns the one aggregate session Live Activity (UX_IOS §5.3): started when work exists,
|
||
|
|
/// updated as sessions change, ended when everything is idle or the device unpairs. State
|
||
|
|
/// flows in from `RemoteStore` on every session-list change; the widget extension renders it
|
||
|
|
/// (`SessionLiveActivity`).
|
||
|
|
@MainActor
|
||
|
|
final class LiveActivityManager {
|
||
|
|
static let shared = LiveActivityManager()
|
||
|
|
private init() {}
|
||
|
|
|
||
|
|
private var activity: Activity<NucleicSessionAttributes>?
|
||
|
|
|
||
|
|
/// Reconcile the Activity with the current session set.
|
||
|
|
func sync(hostName: String, sessions: [WireSessionSummary]) {
|
||
|
|
guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
|
||
|
|
let live = sessions.filter { !$0.archived }
|
||
|
|
let running = live.filter { $0.status == .running || $0.status == .provisioning }
|
||
|
|
let needsYou = live.filter { $0.status.needsYou($0.disposition) }
|
||
|
|
|
||
|
|
guard !running.isEmpty || !needsYou.isEmpty else {
|
||
|
|
end()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// The most urgent session headlines: the top waiter, else the freshest runner.
|
||
|
|
let top = needsYou.max { $0.updatedAt < $1.updatedAt }
|
||
|
|
?? running.max { $0.updatedAt < $1.updatedAt }
|
||
|
|
let state = NucleicSessionAttributes.ContentState(
|
||
|
|
runningCount: running.count,
|
||
|
|
needsYouCount: needsYou.count,
|
||
|
|
topTitle: top?.title,
|
||
|
|
topNeedsYou: top.map { $0.status.needsYou($0.disposition) } ?? false)
|
||
|
|
|
||
|
|
if let activity {
|
||
|
|
Task { await activity.update(ActivityContent(state: state, staleDate: nil)) }
|
||
|
|
} else {
|
||
|
|
// Recover an Activity that survived an app relaunch before starting a new one.
|
||
|
|
if let existing = Activity<NucleicSessionAttributes>.activities.first {
|
||
|
|
activity = existing
|
||
|
|
Task { await existing.update(ActivityContent(state: state, staleDate: nil)) }
|
||
|
|
return
|
||
|
|
}
|
||
|
|
activity = try? Activity.request(
|
||
|
|
attributes: NucleicSessionAttributes(hostName: hostName),
|
||
|
|
content: ActivityContent(state: state, staleDate: nil))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// End the Activity (all idle, or unpaired).
|
||
|
|
func end() {
|
||
|
|
guard let activity else { return }
|
||
|
|
self.activity = nil
|
||
|
|
Task {
|
||
|
|
await activity.end(
|
||
|
|
ActivityContent(state: activity.content.state, staleDate: nil),
|
||
|
|
dismissalPolicy: .immediate)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|