Neues Icon, parallele Downloads
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
#if os(iOS)
|
||||
import ActivityKit
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "ABS", category: "LiveActivity")
|
||||
|
||||
/// Manages one `Activity<DownloadActivityAttributes>` per active download key.
|
||||
/// All methods must be called from the MainActor (same isolation as DownloadManager).
|
||||
@MainActor
|
||||
final class DownloadActivityManager {
|
||||
|
||||
// Keyed by downloadKey (matches DownloadManager's key scheme).
|
||||
private var activities: [String: Activity<DownloadActivityAttributes>] = [:]
|
||||
|
||||
/// Requests a new Live Activity for a download that is starting.
|
||||
/// Logs the reason to the console (visible in Xcode) if the request fails.
|
||||
func startActivity(downloadKey: String, itemTitle: String) {
|
||||
let authInfo = ActivityAuthorizationInfo()
|
||||
guard authInfo.areActivitiesEnabled else {
|
||||
logger.warning("Live Activities disabled (areActivitiesEnabled=false) — skipping for '\(itemTitle)'")
|
||||
return
|
||||
}
|
||||
|
||||
let attributes = DownloadActivityAttributes(itemTitle: itemTitle)
|
||||
let initial = DownloadActivityAttributes.ContentState(
|
||||
progress: 0,
|
||||
bytesDownloaded: 0
|
||||
)
|
||||
let content = ActivityContent(state: initial, staleDate: nil)
|
||||
|
||||
do {
|
||||
let activity = try Activity.request(attributes: attributes, content: content)
|
||||
activities[downloadKey] = activity
|
||||
logger.info("Live Activity started: id=\(activity.id) title='\(itemTitle)'")
|
||||
} catch {
|
||||
// Most common reason: no widget extension is installed/embedded that
|
||||
// provides an ActivityConfiguration for DownloadActivityAttributes.
|
||||
logger.error("Activity.request() failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes an incremental progress update to the running Live Activity.
|
||||
func updateActivity(downloadKey: String, progress: Double, bytesDownloaded: Int64) {
|
||||
guard let activity = activities[downloadKey] else { return }
|
||||
let state = DownloadActivityAttributes.ContentState(
|
||||
progress: progress,
|
||||
bytesDownloaded: bytesDownloaded
|
||||
)
|
||||
let content = ActivityContent(state: state, staleDate: nil)
|
||||
// activity.update() is async; fire-and-forget is intentional — a skipped
|
||||
// frame doesn't hurt the download and avoids blocking the download loop.
|
||||
Task { await activity.update(content) }
|
||||
}
|
||||
|
||||
/// Ends the Live Activity with `.immediate` dismissal so the Lock Screen
|
||||
/// badge disappears right away instead of lingering for the default 4 hours.
|
||||
func endActivity(downloadKey: String) {
|
||||
guard let activity = activities.removeValue(forKey: downloadKey) else { return }
|
||||
Task { await activity.end(nil, dismissalPolicy: .immediate) }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user