Neues Icon, parallele Downloads

This commit is contained in:
Scarriffle
2026-06-08 19:32:18 +02:00
parent 9497c6e315
commit f053f1d320
7 changed files with 316 additions and 0 deletions

View File

@@ -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