import ActivityKit import SwiftUI import WidgetKit import FlixyDesign import FlixyDownloads /// Der Download-Fortschritt im Sperrbildschirm und in der Dynamic Island. struct DownloadLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: DownloadActivityAttributes.self) { context in lockScreen(context.state) // Die Erweiterung liest die Akzentfarbe nicht: sie läuft in // einem eigenen Prozess ohne Zugriff auf die Voreinstellungen // der App. Ein Gruppen-Container wäre der Weg dafür — für // eine Fortschrittsanzeige ist das mehr Aufwand als Nutzen. .tint(.white) } dynamicIsland: { context in DynamicIsland { DynamicIslandExpandedRegion(.leading) { Image(systemName: "arrow.down.circle.fill") .foregroundStyle(.white) } DynamicIslandExpandedRegion(.center) { Text(context.state.title) .font(.caption) .lineLimit(1) } DynamicIslandExpandedRegion(.bottom) { progressBar(context.state) } } compactLeading: { Image(systemName: "arrow.down") } compactTrailing: { if let progress = context.state.progress { Text(progress.formatted(.percent.precision(.fractionLength(0)))) .font(.caption2) .monospacedDigit() } } minimal: { Image(systemName: "arrow.down") } } } private func lockScreen(_ state: DownloadActivityAttributes.ContentState) -> some View { VStack(alignment: .leading, spacing: 8) { HStack { Label(state.title, systemImage: "arrow.down.circle") .font(.headline) .lineLimit(1) Spacer() if state.remainingCount > 0 { Text("+\(state.remainingCount)") .font(.caption) .foregroundStyle(.secondary) } } progressBar(state) HStack { Text(state.receivedBytes.formatted(.byteCount(style: .file))) Spacer() if state.totalBytes > 0 { Text(state.totalBytes.formatted(.byteCount(style: .file))) } } .font(.caption2) .monospacedDigit() .foregroundStyle(.secondary) } .padding() .activityBackgroundTint(Color(red: 0.05, green: 0.05, blue: 0.06)) } @ViewBuilder private func progressBar(_ state: DownloadActivityAttributes.ContentState) -> some View { // Ohne bekannte Gesamtgröße ein unbestimmter Balken: einer bei null // Prozent, der sich nie bewegt, sieht im Sperrbildschirm aus wie ein // hängender Download. if let progress = state.progress { ProgressView(value: progress) } else { ProgressView() } } }