mirror of
https://github.com/X1a0He/Adobe-Downloader.git
synced 2025-11-25 11:18:53 +08:00
refactor: DownloadProgressView, InstallProgressView, AboutView
- Refactoring download logic and dependency crawling logic.
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
||||
@@ -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. 因为依赖产品没有core和non-core之分的
|
||||
2. 如果没有Condition,就下载
|
||||
3. 如果有Condition,目前分析到的基本上是语言之分
|
||||
i. 判断是否为目标语言
|
||||
|
||||
|
||||
PS: 下面是留给看源码的人的
|
||||
哪怕是官方的ACC下载任何一款App,都是这个逻辑,不信自己去翻,你可能会说,为什么官方能下通用的,你问这个问题之前,可以自己去拿正版的看看他是怎么下载的,他下载的包数量跟我的是不是一致的,他也只是下载了对应架构的包
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" : {
|
||||
|
||||
Reference in New Issue
Block a user