mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-07 17:10:29 +00:00
237 lines
5.6 KiB
Swift
237 lines
5.6 KiB
Swift
import ActivityKit
|
|
import SwiftUI
|
|
import WidgetKit
|
|
|
|
@main
|
|
struct GopeedLiveActivityBundle: WidgetBundle {
|
|
var body: some Widget {
|
|
GopeedLiveActivityWidget()
|
|
}
|
|
}
|
|
|
|
struct GopeedLiveActivityWidget: Widget {
|
|
|
|
var body: some WidgetConfiguration {
|
|
|
|
ActivityConfiguration(
|
|
for: GopeedDownloadAttributes.self
|
|
) { context in
|
|
|
|
// MARK: Lock Screen / notification banner
|
|
|
|
VStack(spacing: 10) {
|
|
|
|
HStack {
|
|
|
|
Image(systemName: statusIcon(context.state.status))
|
|
.font(.title3)
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
|
|
Text(context.attributes.fileName)
|
|
.font(.headline)
|
|
.lineLimit(1)
|
|
|
|
Text(statusText(context.state.status))
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Text(
|
|
"\(Int(context.state.progress * 100))%"
|
|
)
|
|
.font(.headline)
|
|
.monospacedDigit()
|
|
}
|
|
|
|
progressView(context)
|
|
|
|
HStack {
|
|
|
|
Text(
|
|
"\(formatBytes(context.state.downloaded)) / \(formatBytes(context.state.total))"
|
|
)
|
|
|
|
Spacer()
|
|
|
|
if context.state.speed > 0 {
|
|
Text(
|
|
"\(formatBytes(context.state.speed))/s"
|
|
)
|
|
}
|
|
}
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.padding()
|
|
.activityBackgroundTint(.black.opacity(0.85))
|
|
.activitySystemActionForegroundColor(.white)
|
|
|
|
} dynamicIsland: { context in
|
|
|
|
DynamicIsland {
|
|
|
|
// MARK: Expanded Dynamic Island
|
|
|
|
DynamicIslandExpandedRegion(.leading) {
|
|
Image(
|
|
systemName: statusIcon(
|
|
context.state.status
|
|
)
|
|
)
|
|
.font(.title3)
|
|
}
|
|
|
|
DynamicIslandExpandedRegion(.center) {
|
|
Text(context.attributes.fileName)
|
|
.lineLimit(1)
|
|
.font(.headline)
|
|
}
|
|
|
|
DynamicIslandExpandedRegion(.trailing) {
|
|
Text(
|
|
"\(Int(context.state.progress * 100))%"
|
|
)
|
|
.monospacedDigit()
|
|
.font(.headline)
|
|
}
|
|
|
|
DynamicIslandExpandedRegion(.bottom) {
|
|
|
|
VStack(spacing: 6) {
|
|
|
|
progressView(context)
|
|
|
|
HStack {
|
|
|
|
Text(
|
|
"\(formatBytes(context.state.downloaded)) / \(formatBytes(context.state.total))"
|
|
)
|
|
|
|
Spacer()
|
|
|
|
if context.state.speed > 0 {
|
|
Text(
|
|
"\(formatBytes(context.state.speed))/s"
|
|
)
|
|
}
|
|
}
|
|
.font(.caption2)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
|
|
} compactLeading: {
|
|
|
|
Image(
|
|
systemName: statusIcon(
|
|
context.state.status
|
|
)
|
|
)
|
|
|
|
} compactTrailing: {
|
|
|
|
Text(
|
|
"\(Int(context.state.progress * 100))%"
|
|
)
|
|
.monospacedDigit()
|
|
|
|
} minimal: {
|
|
|
|
Image(systemName: "arrow.down")
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: - Progress bar
|
|
|
|
@ViewBuilder
|
|
private func progressView(
|
|
_ context:
|
|
ActivityViewContext<GopeedDownloadAttributes>
|
|
) -> some View {
|
|
|
|
if context.state.usesEstimatedProgress,
|
|
context.state.estimatedEnd >
|
|
context.state.estimatedStart {
|
|
|
|
ProgressView(
|
|
timerInterval:
|
|
context.state.estimatedStart
|
|
...
|
|
context.state.estimatedEnd,
|
|
countsDown: false
|
|
)
|
|
|
|
} else {
|
|
|
|
ProgressView(
|
|
value: context.state.progress,
|
|
total: 1.0
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: - Formatting
|
|
|
|
private func formatBytes(
|
|
_ bytes: Int64
|
|
) -> String {
|
|
|
|
guard bytes > 0 else {
|
|
return "0 B"
|
|
}
|
|
|
|
return ByteCountFormatter.string(
|
|
fromByteCount: bytes,
|
|
countStyle: .file
|
|
)
|
|
}
|
|
|
|
|
|
private func statusIcon(
|
|
_ status: String
|
|
) -> String {
|
|
|
|
switch status {
|
|
|
|
case "done":
|
|
return "checkmark.circle.fill"
|
|
|
|
case "error":
|
|
return "exclamationmark.triangle.fill"
|
|
|
|
case "pause", "paused":
|
|
return "pause.circle.fill"
|
|
|
|
default:
|
|
return "arrow.down.circle.fill"
|
|
}
|
|
}
|
|
|
|
|
|
private func statusText(
|
|
_ status: String
|
|
) -> String {
|
|
|
|
switch status {
|
|
|
|
case "done":
|
|
return "Download complete"
|
|
|
|
case "error":
|
|
return "Download failed"
|
|
|
|
case "pause", "paused":
|
|
return "Paused"
|
|
|
|
default:
|
|
return "Downloading"
|
|
}
|
|
}
|
|
}
|