Files
nucleic-remote-ios/NucleicRemote/NucleicRemote/LiveActivityManager.swift
T

63 lines
2.5 KiB
Swift
Raw Normal View History

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)
}
}
}