NuvioStreaming/iosApp/iosApp/DownloadsLiveActivityManager.swift
2026-04-12 00:40:28 +05:30

129 lines
3.8 KiB
Swift

import Foundation
#if canImport(ActivityKit) && os(iOS) && !targetEnvironment(macCatalyst)
import ActivityKit
#endif
private let downloadsLiveStatusUpdatedNotification = Notification.Name("NuvioDownloadsLiveStatusUpdated")
private let downloadsLiveStatusPayloadKey = "nuvio.downloads.live_status.payload"
final class DownloadsLiveActivityManager {
static let shared = DownloadsLiveActivityManager()
private var observer: NSObjectProtocol?
private init() {}
func start() {
guard observer == nil else { return }
observer = NotificationCenter.default.addObserver(
forName: downloadsLiveStatusUpdatedNotification,
object: nil,
queue: .main
) { [weak self] _ in
self?.syncFromPayloadStore()
}
syncFromPayloadStore()
}
private func syncFromPayloadStore() {
#if canImport(ActivityKit) && os(iOS) && !targetEnvironment(macCatalyst)
guard #available(iOS 16.1, *) else { return }
let payload = loadPayload()
Task {
await apply(payload)
}
#endif
}
private func loadPayload() -> DownloadsLiveStatusPayload? {
guard let encoded = UserDefaults.standard.string(forKey: downloadsLiveStatusPayloadKey) else {
return nil
}
let data = Data(encoded.utf8)
return try? JSONDecoder().decode(DownloadsLiveStatusPayload.self, from: data)
}
#if canImport(ActivityKit) && os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 16.1, *)
private func apply(_ payload: DownloadsLiveStatusPayload?) async {
let existing = Activity<DownloadsLiveActivityAttributes>.activities.first
guard let payload else {
if let existing {
await existing.end(dismissalPolicy: .immediate)
}
return
}
let state = DownloadsLiveActivityAttributes.ContentState(
status: payload.status,
progressPercent: payload.progressPercent,
transferredText: transferredText(payload)
)
if let existing, existing.attributes.downloadId == payload.id {
await existing.update(using: state)
return
}
if let existing {
await existing.end(dismissalPolicy: .immediate)
}
let attributes = DownloadsLiveActivityAttributes(
downloadId: payload.id,
title: payload.title,
subtitle: payload.subtitle
)
_ = try? Activity<DownloadsLiveActivityAttributes>.request(
attributes: attributes,
contentState: state,
pushType: nil
)
}
#endif
private func transferredText(_ payload: DownloadsLiveStatusPayload) -> String {
let downloaded = formatBytes(payload.downloadedBytes)
if let total = payload.totalBytes {
return "\(downloaded) / \(formatBytes(total))"
}
return downloaded
}
private func formatBytes(_ bytes: Int64) -> String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useKB, .useMB, .useGB]
formatter.countStyle = .file
return formatter.string(fromByteCount: bytes)
}
}
#if canImport(ActivityKit) && os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 16.1, *)
struct DownloadsLiveActivityAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
let status: String
let progressPercent: Int
let transferredText: String
}
let downloadId: String
let title: String
let subtitle: String
}
#endif
private struct DownloadsLiveStatusPayload: Decodable {
let id: String
let title: String
let subtitle: String
let status: String
let downloadedBytes: Int64
let totalBytes: Int64?
let progressPercent: Int
}