d94054dd1a
- 项目从 SPM (Sources/VODManager/) 迁移到 Xcode 构建系统 (VODManager.xcodeproj) - 源码目录从 Sources/VODManager/ 迁移至 VODManager/VODManager/ - 用 Localizable.xcstrings 替代旧的 .lproj/Localizable.strings 文件 - 删除已废弃的 apps/web、apps/server、docker 目录及相关文档 - RTL 侧边栏修复:切语言时用 .id() 强制重建 NavigationSplitView - 视频库无账号时不发起 API 请求和重试 - 无账号时显示 ContentUnavailableView 提示
120 lines
3.8 KiB
Swift
120 lines
3.8 KiB
Swift
import SwiftUI
|
|
import SwiftData
|
|
|
|
struct DownloadTaskListView: View {
|
|
@EnvironmentObject var accountStore: AccountStore
|
|
@Query(sort: \DownloadTask.createdAt, order: .reverse) private var tasks: [DownloadTask]
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
if tasks.isEmpty {
|
|
ContentUnavailableView(
|
|
"download_task.empty_title",
|
|
systemImage: "arrow.down.circle",
|
|
description: Text("download_task.empty_desc")
|
|
)
|
|
} else {
|
|
HStack {
|
|
Text(verbatim: String(format: String(localized: "download_task.total"), tasks.count))
|
|
.foregroundStyle(.secondary)
|
|
Spacer()
|
|
Button("download_task.clear_completed") {
|
|
Task { await DatabaseManager.shared.clearCompletedDownloadTasks() }
|
|
}
|
|
.buttonStyle(.bordered)
|
|
}
|
|
.padding(.horizontal)
|
|
.padding(.vertical, 8)
|
|
|
|
List {
|
|
ForEach(tasks) { task in
|
|
DownloadTaskRow(task: task)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("nav.download_tasks")
|
|
}
|
|
}
|
|
|
|
struct DownloadTaskRow: View {
|
|
let task: DownloadTask
|
|
|
|
@State private var showDeleteConfirm = false
|
|
|
|
var body: some View {
|
|
HStack {
|
|
statusIcon
|
|
VStack(alignment: .leading) {
|
|
Text(task.title)
|
|
.font(.headline)
|
|
}
|
|
Spacer()
|
|
statusText
|
|
if task.status == .completed {
|
|
Button("btn.open") {
|
|
if let path = task.localPath {
|
|
NSWorkspace.shared.selectFile(path, inFileViewerRootedAtPath: "")
|
|
}
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.controlSize(.small)
|
|
}
|
|
}
|
|
.padding(.vertical, 4)
|
|
.swipeActions(edge: .trailing) {
|
|
Button {
|
|
showDeleteConfirm = true
|
|
} label: {
|
|
Label("btn.delete", systemImage: "trash")
|
|
}
|
|
}
|
|
.alert("download_task.delete_confirm_title", isPresented: $showDeleteConfirm) {
|
|
Button("btn.cancel", role: .cancel) {}
|
|
Button("btn.delete", role: .destructive) {
|
|
Task { await DatabaseManager.shared.deleteDownloadTask(task) }
|
|
}
|
|
} message: {
|
|
Text(verbatim: String(format: String(localized: "download_task.delete_confirm_msg"), task.title))
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var statusIcon: some View {
|
|
switch task.status {
|
|
case .pending:
|
|
Image(systemName: "clock.fill")
|
|
.foregroundColor(.gray)
|
|
case .downloading:
|
|
Image(systemName: "arrow.down.circle.fill")
|
|
.foregroundColor(.blue)
|
|
case .completed:
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
case .failed:
|
|
Image(systemName: "exclamationmark.circle.fill")
|
|
.foregroundColor(.red)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var statusText: some View {
|
|
switch task.status {
|
|
case .pending:
|
|
Text("download_task.status.pending")
|
|
case .downloading:
|
|
ProgressView(value: task.progress)
|
|
.frame(width: 80)
|
|
case .completed:
|
|
Text("download_task.status.completed")
|
|
case .failed:
|
|
VStack(alignment: .trailing) {
|
|
Text("download_task.status.failed")
|
|
Text(task.errorMessage ?? "")
|
|
.font(.caption2)
|
|
.foregroundStyle(.red)
|
|
}
|
|
}
|
|
}
|
|
}
|