refactor: DownloadProgressView, InstallProgressView, AboutView

- Refactoring download logic and dependency crawling logic.
This commit is contained in:
X1a0He
2025-03-09 00:41:17 +08:00
parent 3c6c3b4857
commit 5b5de8900e
6 changed files with 441 additions and 189 deletions

View File

@@ -31,7 +31,7 @@
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

View File

@@ -161,13 +161,21 @@ class NewDownloadUtils {
var nonCorePackageCount = 0
/*
1. Condition
2. Condition
[OSVersion]>=10.15 : 10.15
[OSArchitecture]==arm64 : arm64
[OSArchitecture]==x64 :
[installLanguage]==zh_CN : zh_CN
1. non-core
2. core
a. Condition
b. Condition
i.
ii.
1. corenon-core
2. Condition
3. Condition
i.
PS:
ACCApp
@@ -204,6 +212,34 @@ class NewDownloadUtils {
downloadSize = Int64(sizeInt)
} else { continue }
if dependencyToDownload.sapCode != productInfo.id {
}
if dependencyToDownload.sapCode == productInfo.id {
if isCore {
let installLanguage = "[installLanguage]==\(task.language)"
if let condition = package["Condition"] as? String {
if condition.isEmpty {
shouldDownload = true
} else {
if condition.contains("[OSArchitecture]==\(AppStatics.architectureSymbol)") {
shouldDownload = true
}
// if condition.contains("[OSArchitecture]==x64") {
// shouldDownload = true
// }
if condition.contains(installLanguage) || task.language == "ALL" {
shouldDownload = true
}
}
} else {
shouldDownload = true
}
} else {
shouldDownload = false
}
} else {
let installLanguage = "[installLanguage]==\(task.language)"
if let condition = package["Condition"] as? String {
if condition.isEmpty {
@@ -241,12 +277,6 @@ class NewDownloadUtils {
}
}
}
if condition.contains("[OSArchitecture]==\(AppStatics.architectureSymbol)") {
shouldDownload = true
}
if condition.contains("[OSArchitecture]==x64") {
shouldDownload = true
}
if condition.contains(installLanguage) || task.language == "ALL" {
shouldDownload = true
}
@@ -254,6 +284,7 @@ class NewDownloadUtils {
} else {
shouldDownload = true
}
}
if isCore {
corePackageCount += 1

View File

@@ -1890,77 +1890,174 @@ struct CleanupView: View {
Divider()
.padding(.vertical, 8)
VStack(alignment: .leading, spacing: 8) {
VStack(alignment: .leading, spacing: 12) {
if isProcessing {
VStack(alignment: .leading, spacing: 10) {
HStack {
ProgressView(value: Double(currentCommandIndex), total: Double(totalCommands)) {
Text("清理进度:\(currentCommandIndex)/\(totalCommands)")
.font(.system(size: 12))
.font(.system(size: 12, weight: .medium))
Spacer()
let percentage = totalCommands > 0 ? Int((Double(currentCommandIndex) / Double(totalCommands)) * 100) : 0
Text("\(percentage)%")
.font(.system(size: 12, weight: .bold))
.foregroundColor(.blue)
}
.progressViewStyle(LinearProgressViewStyle())
.padding(.horizontal, 2)
GeometryReader { geometry in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 10)
.fill(Color.secondary.opacity(0.2))
.frame(height: 12)
let progressWidth = totalCommands > 0 ?
CGFloat(Double(currentCommandIndex) / Double(totalCommands)) * geometry.size.width : 0
RoundedRectangle(cornerRadius: 10)
.fill(
LinearGradient(
gradient: Gradient(colors: [Color.blue.opacity(0.8), Color.blue]),
startPoint: .leading,
endPoint: .trailing
)
)
.frame(width: progressWidth, height: 12)
}
}
.frame(height: 12)
.animation(.linear(duration: 0.3), value: currentCommandIndex)
Button(action: {
isCancelled = true
}) {
Text("取消")
Text("取消清理")
.font(.system(size: 12, weight: .medium))
.foregroundColor(.white)
}
.buttonStyle(BeautifulButtonStyle(baseColor: Color.red.opacity(0.8)))
.disabled(isCancelled)
.opacity(isCancelled ? 0.5 : 1)
}
.padding(10)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color(NSColor.controlBackgroundColor))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.blue.opacity(0.2), lineWidth: 1)
)
if let lastLog = cleanupLogs.last {
VStack(alignment: .leading, spacing: 4) {
HStack(spacing: 8) {
Image(systemName: "arrow.triangle.turn.up.right.circle.fill")
.foregroundColor(.blue.opacity(0.8))
.font(.system(size: 14))
Text("当前执行:")
.font(.system(size: 12, weight: .medium))
.foregroundColor(.secondary)
}
#if DEBUG
Text("当前执行:\(lastLog.command)")
.font(.system(size: 12))
Text(lastLog.command)
.font(.system(size: 11))
.foregroundColor(.secondary)
.lineLimit(1)
.lineLimit(2)
.truncationMode(.middle)
.frame(maxWidth: .infinity, alignment: .leading)
#else
Text("当前执行:\(CleanupLog.getCleanupDescription(for: lastLog.command))")
.font(.system(size: 12))
Text(CleanupLog.getCleanupDescription(for: lastLog.command))
.font(.system(size: 11))
.foregroundColor(.secondary)
.lineLimit(1)
.lineLimit(2)
.truncationMode(.middle)
.frame(maxWidth: .infinity, alignment: .leading)
#endif
}
.frame(height: 70)
.padding(.vertical, 6)
.padding(.horizontal, 8)
.frame(maxWidth: .infinity)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(Color(NSColor.textBackgroundColor))
)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
)
}
}
VStack(alignment: .leading, spacing: 4) {
VStack(alignment: .leading, spacing: 0) {
Button(action: {
withAnimation {
isLogExpanded.toggle()
}
}) {
HStack {
Image(systemName: "terminal.fill")
.font(.system(size: 12))
.foregroundColor(.blue.opacity(0.8))
Text("最近日志:")
.font(.system(size: 12, weight: .medium))
if isProcessing {
HStack(spacing: 4) {
Circle()
.fill(Color.green)
.frame(width: 6, height: 6)
Text("正在执行...")
.font(.system(size: 12))
.foregroundColor(.secondary)
}
.padding(.horizontal, 4)
.padding(.vertical, 2)
.background(Color.secondary.opacity(0.1))
.cornerRadius(4)
}
Spacer()
Text(isLogExpanded ? "收起" : "展开")
.font(.system(size: 11))
.foregroundColor(.blue)
.padding(.trailing, 4)
Image(systemName: isLogExpanded ? "chevron.down" : "chevron.right")
.foregroundColor(.secondary)
.font(.system(size: 10))
.foregroundColor(.blue)
}
.padding(.vertical, 4)
.padding(.vertical, 8)
.padding(.horizontal, 10)
.background(Color(NSColor.controlBackgroundColor))
.contentShape(Rectangle())
}
.buttonStyle(.plain)
ScrollView {
VStack(alignment: .leading, spacing: 4) {
VStack(alignment: .leading, spacing: 8) {
if cleanupLogs.isEmpty {
HStack {
Spacer()
VStack(spacing: 6) {
Image(systemName: "doc.text.magnifyingglass")
.font(.system(size: 20))
.foregroundColor(.secondary.opacity(0.6))
Text("暂无清理记录")
.font(.system(size: 11))
.foregroundColor(.secondary)
}
.padding(.vertical, 16)
Spacer()
}
.padding(.vertical, 8)
} else {
if isLogExpanded {
ForEach(cleanupLogs.reversed()) { log in
@@ -1971,14 +2068,23 @@ struct CleanupView: View {
}
}
}
.padding(.vertical, 4)
.padding(.vertical, 6)
.padding(.horizontal, 2)
}
.frame(height: cleanupLogs.isEmpty ? 40 : (isLogExpanded ? 200 : 40))
.animation(.easeInOut, value: isLogExpanded)
}
.padding(8)
.background(Color(NSColor.textBackgroundColor))
.frame(height: cleanupLogs.isEmpty ? 80 : (isLogExpanded ? 220 : 54))
.animation(.easeInOut(duration: 0.3), value: isLogExpanded)
.background(Color(NSColor.textBackgroundColor).opacity(0.6))
.cornerRadius(6)
.padding(.bottom, 1)
}
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color(NSColor.textBackgroundColor))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
)
}
HStack(spacing: 10) {
@@ -2289,32 +2395,3 @@ struct LogEntryView: View {
return formatter.string(from: date)
}
}
#Preview("About Tab") {
AboutAppView()
}
#Preview("General Settings") {
let networkManager = NetworkManager()
VStack {
GeneralSettingsView(updater: PreviewUpdater())
.environmentObject(networkManager)
}
.fixedSize()
}
#Preview("Q&A View") {
QAView()
.frame(width: 600)
}
#Preview("Cleanup View") {
CleanupView()
.frame(width: 600)
}
#Preview("Complete About View") {
AboutView(updater: PreviewUpdater())
.environmentObject(NetworkManager())
}

View File

@@ -784,6 +784,7 @@ struct ProductRow: View {
@ObservedObject var product: DependenciesToDownload
let isCurrentProduct: Bool
@Binding var expandedProducts: Set<String>
@State private var showCopiedAlert = false
var body: some View {
VStack(alignment: .leading, spacing: 4) {
@@ -805,6 +806,33 @@ struct ProductRow: View {
.font(.system(size: 12, weight: .semibold))
.foregroundColor(.primary.opacity(0.8))
if product.sapCode != "APRO" {
Button(action: {
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(product.buildGuid, forType: .string)
showCopiedAlert = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
showCopiedAlert = false
}
}) {
Image(systemName: "doc.on.doc")
.font(.system(size: 10))
.foregroundColor(.white)
}
.buttonStyle(BeautifulButtonStyle(baseColor: .blue))
.help("复制 buildGuid")
.popover(isPresented: $showCopiedAlert, arrowEdge: .trailing) {
Text("已复制")
.font(.system(size: 14, weight: .medium))
.foregroundColor(.white)
.padding(.horizontal, 12)
.padding(.vertical, 8)
.cornerRadius(6)
.padding(6)
}
}
Spacer()
Text("\(product.completedPackages)/\(product.totalPackages)")
@@ -845,6 +873,7 @@ struct ProductRow: View {
}
}
}
}
struct PackageRow: View {

View File

@@ -73,6 +73,7 @@ struct InstallProgressView: View {
if !isFailed {
ProgressSection(progress: progress, progressText: progressText)
.padding(.vertical, 4)
}
if isFailed {
@@ -91,8 +92,11 @@ struct InstallProgressView: View {
}
.padding()
.frame(minWidth: 500)
.background(Color(NSColor.windowBackgroundColor))
.cornerRadius(8)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color(NSColor.windowBackgroundColor))
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
)
}
}
@@ -101,15 +105,40 @@ private struct ProgressSection: View {
let progressText: String
var body: some View {
VStack(spacing: 4) {
ProgressView(value: progress)
.progressViewStyle(.linear)
.frame(maxWidth: .infinity)
VStack(spacing: 8) {
HStack {
Spacer()
Text(progressText)
.font(.caption)
.foregroundColor(.secondary)
.frame(maxWidth: .infinity, alignment: .center)
.font(.system(size: 12, weight: .bold))
.foregroundColor(.blue)
.padding(.vertical, 2)
.padding(.horizontal, 8)
.background(Color.blue.opacity(0.1))
.cornerRadius(4)
}
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.fill(Color.secondary.opacity(0.1))
.frame(height: 6)
.cornerRadius(3)
Rectangle()
.fill(
LinearGradient(
gradient: Gradient(colors: [Color.blue.opacity(0.7), Color.blue]),
startPoint: .leading,
endPoint: .trailing
)
)
.frame(width: max(0, CGFloat(progress) * geometry.size.width), height: 6)
.cornerRadius(3)
.animation(.linear(duration: 0.3), value: progress)
}
}
.frame(height: 6)
}
.padding(.horizontal, 20)
}
@@ -120,27 +149,44 @@ private struct ErrorSection: View {
let isFailed: Bool
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("错误详情:")
.font(.caption)
.foregroundColor(.secondary)
.fontWeight(.medium)
VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 6) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
.font(.system(size: 14))
Text("错误详情")
.font(.system(size: 13, weight: .medium))
.foregroundColor(.primary.opacity(0.8))
}
.padding(.vertical, 2)
Text(status)
.font(.caption)
.font(.system(size: 12))
.foregroundColor(.secondary)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(8)
.background(Color.secondary.opacity(0.1))
.cornerRadius(6)
.padding(10)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.red.opacity(0.05))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(Color.red.opacity(0.2), lineWidth: 1)
)
if isFailed {
HStack {
Text("自行安装命令:")
.font(.caption)
.foregroundColor(.secondary)
.fontWeight(.medium)
HStack(spacing: 6) {
Image(systemName: "terminal.fill")
.foregroundColor(.blue.opacity(0.7))
.font(.system(size: 14))
Text("自行安装命令")
.font(.system(size: 13, weight: .medium))
.foregroundColor(.primary.opacity(0.8))
Spacer()
CommandPopover()
}
.padding(.vertical, 2)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -185,29 +231,33 @@ private struct ButtonSection: View {
if isFailed {
if let onRetry = onRetry {
Button(action: onRetry) {
Label("重试", systemImage: "arrow.clockwise.circle.fill")
Label("重试", systemImage: "arrow.clockwise")
.font(.system(size: 13, weight: .medium))
.foregroundColor(.white)
}
.buttonStyle(.borderedProminent)
.tint(.blue)
.buttonStyle(BeautifulButtonStyle(baseColor: .blue))
}
Button(action: onCancel) {
Label("关闭", systemImage: "xmark.circle.fill")
Label("关闭", systemImage: "xmark")
.font(.system(size: 13, weight: .medium))
.foregroundColor(.white)
}
.buttonStyle(.borderedProminent)
.tint(.red)
.buttonStyle(BeautifulButtonStyle(baseColor: .red))
} else if isCompleted {
Button(action: onCancel) {
Label("关闭", systemImage: "xmark.circle.fill")
Label("关闭", systemImage: "xmark")
.font(.system(size: 13, weight: .medium))
.foregroundColor(.white)
}
.buttonStyle(.borderedProminent)
.tint(.green)
.buttonStyle(BeautifulButtonStyle(baseColor: .green))
} else {
Button(action: onCancel) {
Label("取消", systemImage: "xmark.circle.fill")
Label("取消", systemImage: "xmark")
.font(.system(size: 13, weight: .medium))
.foregroundColor(.white)
}
.buttonStyle(.borderedProminent)
.tint(.red)
.buttonStyle(BeautifulButtonStyle(baseColor: .red))
}
}
.padding(.horizontal, 20)
@@ -221,13 +271,25 @@ private struct CommandPopover: View {
var body: some View {
Button(action: { showPopover.toggle() }) {
Image(systemName: "terminal.fill")
.foregroundColor(.secondary)
Text("查看")
.font(.system(size: 12, weight: .medium))
.foregroundColor(.white)
.padding(.vertical, 4)
.padding(.horizontal, 8)
.background(Color.blue.opacity(0.8))
.cornerRadius(4)
}
.buttonStyle(.plain)
.popover(isPresented: $showPopover, arrowEdge: .bottom) {
VStack(alignment: .leading, spacing: 8) {
Button("复制命令") {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("安装命令")
.font(.headline)
.foregroundColor(.primary)
Spacer()
Button(action: {
let command = networkManager.installCommand
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
@@ -237,25 +299,47 @@ private struct CommandPopover: View {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
showCopiedAlert = false
}
}) {
Label("复制", systemImage: "doc.on.doc")
.font(.system(size: 12, weight: .medium))
.foregroundColor(.white)
}
.buttonStyle(BeautifulButtonStyle(baseColor: .blue))
}
if showCopiedAlert {
Text("已复制")
.font(.caption)
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("命令已复制到剪贴板")
.font(.system(size: 12))
.foregroundColor(.green)
}
.padding(6)
.background(Color.green.opacity(0.1))
.cornerRadius(4)
.transition(.opacity)
.animation(.easeInOut, value: showCopiedAlert)
}
let command = networkManager.installCommand
Text(command)
.font(.system(.caption, design: .monospaced))
.foregroundColor(.secondary)
.font(.system(size: 12, design: .monospaced))
.foregroundColor(.primary.opacity(0.8))
.textSelection(.enabled)
.padding(8)
.background(Color.secondary.opacity(0.1))
.cornerRadius(6)
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color(NSColor.textBackgroundColor))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(Color.secondary.opacity(0.2), lineWidth: 1)
)
}
.padding()
.frame(width: 400)
.padding(16)
.frame(width: 450)
}
}
}

View File

@@ -26,6 +26,9 @@
}
}
}
},
"(可能导致处理失败)" : {
},
"(将导致无法使用安装功能)" : {
"extractionState" : "stale",
@@ -37,6 +40,9 @@
}
}
}
},
"(无法使用安装功能)" : {
},
"(退出代码: %lld)" : {
@@ -150,12 +156,6 @@
}
}
}
},
"✅" : {
},
"❌" : {
},
"🔔 即将下载 %@ (%@) 版本 🔔" : {
"extractionState" : "stale",
@@ -314,6 +314,7 @@
},
"Debug 模式" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -451,15 +452,9 @@
},
"macOS %@" : {
},
"Match:" : {
},
"OK" : {
},
"Reason:" : {
},
"Setup 组件安装成功" : {
"extractionState" : "stale",
@@ -504,9 +499,6 @@
}
}
}
},
"Target:" : {
},
"v%@" : {
@@ -1134,6 +1126,9 @@
}
}
}
},
"取消清理" : {
},
"可用版本: %lld" : {
"extractionState" : "stale",
@@ -1168,6 +1163,9 @@
},
"可选模块" : {
},
"命令已复制到剪贴板" : {
},
"命令行安装" : {
"localizations" : {
@@ -1214,6 +1212,12 @@
},
"备份状态: " : {
},
"复制" : {
},
"复制 buildGuid" : {
},
"复制命令" : {
"localizations" : {
@@ -1316,6 +1320,9 @@
}
}
}
},
"安装命令" : {
},
"安装失败" : {
"localizations" : {
@@ -1461,6 +1468,7 @@
}
},
"将执行的命令:" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1499,8 +1507,12 @@
}
}
}
},
"展开" : {
},
"展开全部" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1527,7 +1539,6 @@
},
"已处理" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1538,7 +1549,6 @@
}
},
"已备份" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1662,8 +1672,12 @@
}
}
}
},
"当前执行:" : {
},
"当前执行:%@" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1748,6 +1762,7 @@
}
},
"折叠全部" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1836,6 +1851,9 @@
}
}
}
},
"收起" : {
},
"数据无效: %@" : {
"comment" : "Invalid data",
@@ -2088,6 +2106,12 @@
}
}
}
},
"未处理" : {
},
"未备份" : {
},
"未安装" : {
"localizations" : {
@@ -2192,7 +2216,7 @@
"架构或版本不一致 (退出代码: %lld)" : {
},
"查看持久化文件" : {
"查看" : {
},
"检查中" : {
@@ -2832,6 +2856,9 @@
}
}
}
},
"自行安装命令" : {
},
"自行安装命令:" : {
"localizations" : {
@@ -3123,8 +3150,12 @@
}
}
}
},
"错误详情" : {
},
"错误详情:" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {