2024-10-31 22:35:22 +08:00
|
|
|
//
|
|
|
|
|
// Adobe-Downloader
|
|
|
|
|
//
|
|
|
|
|
// Created by X1a0He on 2024/10/30.
|
|
|
|
|
//
|
|
|
|
|
import SwiftUI
|
|
|
|
|
|
|
|
|
|
struct InstallProgressView: View {
|
2024-11-04 17:40:01 +08:00
|
|
|
@EnvironmentObject private var networkManager: NetworkManager
|
2024-10-31 22:35:22 +08:00
|
|
|
let productName: String
|
|
|
|
|
let progress: Double
|
|
|
|
|
let status: String
|
|
|
|
|
let onCancel: () -> Void
|
2024-11-04 00:29:08 +08:00
|
|
|
let onRetry: (() -> Void)?
|
2024-10-31 22:35:22 +08:00
|
|
|
|
|
|
|
|
private var isCompleted: Bool {
|
|
|
|
|
progress >= 1.0 || status == "安装完成"
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-01 17:28:23 +08:00
|
|
|
private var isFailed: Bool {
|
|
|
|
|
status.contains("失败")
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-31 22:35:22 +08:00
|
|
|
private var progressText: String {
|
|
|
|
|
if isCompleted {
|
|
|
|
|
return "安装完成"
|
|
|
|
|
} else {
|
|
|
|
|
return "\(Int(progress * 100))%"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
VStack(spacing: 16) {
|
|
|
|
|
HStack {
|
2024-11-01 17:28:23 +08:00
|
|
|
Image(systemName: isCompleted ? "checkmark.circle.fill" :
|
|
|
|
|
(status.contains("失败") ? "xmark.circle.fill" : "arrow.down.circle.fill"))
|
2024-10-31 22:35:22 +08:00
|
|
|
.font(.title2)
|
2024-11-01 17:28:23 +08:00
|
|
|
.foregroundColor(isCompleted ? .green :
|
|
|
|
|
(status.contains("失败") ? .red : .blue))
|
2024-10-31 22:35:22 +08:00
|
|
|
|
2024-11-01 17:28:23 +08:00
|
|
|
Text(isCompleted ? "\(productName) 安装完成" :
|
|
|
|
|
(status.contains("失败") ? "\(productName) 安装失败" : "正在安装 \(productName)"))
|
2024-10-31 22:35:22 +08:00
|
|
|
.font(.headline)
|
|
|
|
|
}
|
2024-11-04 17:40:01 +08:00
|
|
|
.padding(.horizontal, 20)
|
2024-10-31 22:35:22 +08:00
|
|
|
|
|
|
|
|
VStack(spacing: 4) {
|
2024-11-01 17:28:23 +08:00
|
|
|
if !status.contains("失败") {
|
|
|
|
|
ProgressView(value: progress)
|
|
|
|
|
.progressViewStyle(.linear)
|
|
|
|
|
.frame(maxWidth: .infinity)
|
|
|
|
|
|
|
|
|
|
Text(progressText)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
|
|
|
}
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
2024-11-04 17:40:01 +08:00
|
|
|
.padding(.horizontal, 20)
|
|
|
|
|
|
|
|
|
|
ScrollViewReader { proxy in
|
|
|
|
|
ScrollView {
|
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
|
|
|
ForEach(Array(networkManager.installationLogs.enumerated()), id: \.offset) { index, log in
|
|
|
|
|
Text(log)
|
|
|
|
|
.font(.system(.caption, design: .monospaced))
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
.id(index)
|
|
|
|
|
.padding(.horizontal, 8)
|
|
|
|
|
.padding(.vertical, 2)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
|
|
|
.padding(.vertical, 8)
|
|
|
|
|
}
|
|
|
|
|
.frame(height: 150)
|
|
|
|
|
.background(Color(NSColor.textBackgroundColor))
|
|
|
|
|
.cornerRadius(4)
|
|
|
|
|
.overlay(
|
|
|
|
|
RoundedRectangle(cornerRadius: 4)
|
|
|
|
|
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
|
|
|
|
|
)
|
|
|
|
|
.padding(.horizontal, 20)
|
|
|
|
|
.onChange(of: networkManager.installationLogs) { oldValue, newValue in
|
|
|
|
|
withAnimation {
|
|
|
|
|
proxy.scrollTo(newValue.count - 1, anchor: .bottom)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-31 22:35:22 +08:00
|
|
|
|
2024-11-01 17:28:23 +08:00
|
|
|
if status.contains("失败") {
|
2024-11-04 17:40:01 +08:00
|
|
|
ScrollView(showsIndicators: false) {
|
2024-11-01 17:28:23 +08:00
|
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
|
|
|
Text("错误详情:")
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
.fontWeight(.medium)
|
|
|
|
|
|
|
|
|
|
Text(status)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(.secondary)
|
2024-11-04 00:29:08 +08:00
|
|
|
.textSelection(.enabled)
|
2024-11-01 17:28:23 +08:00
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
|
|
|
.padding(8)
|
|
|
|
|
.background(Color.secondary.opacity(0.1))
|
|
|
|
|
.cornerRadius(6)
|
|
|
|
|
}
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
2024-11-01 17:28:23 +08:00
|
|
|
.frame(maxHeight: 100)
|
2024-11-04 17:40:01 +08:00
|
|
|
.padding(.horizontal, 20)
|
2024-11-01 17:28:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HStack(spacing: 12) {
|
|
|
|
|
if isFailed {
|
|
|
|
|
if let onRetry = onRetry {
|
|
|
|
|
Button(action: onRetry) {
|
|
|
|
|
Label("重试", systemImage: "arrow.clockwise.circle.fill")
|
|
|
|
|
}
|
|
|
|
|
.buttonStyle(.borderedProminent)
|
|
|
|
|
.tint(.blue)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Button(action: onCancel) {
|
|
|
|
|
Label("关闭", systemImage: "xmark.circle.fill")
|
|
|
|
|
}
|
|
|
|
|
.buttonStyle(.borderedProminent)
|
|
|
|
|
.tint(.red)
|
|
|
|
|
} else if isCompleted {
|
|
|
|
|
Button(action: onCancel) {
|
|
|
|
|
Label("关闭", systemImage: "xmark.circle.fill")
|
|
|
|
|
}
|
|
|
|
|
.buttonStyle(.borderedProminent)
|
|
|
|
|
.tint(.green)
|
|
|
|
|
} else {
|
|
|
|
|
Button(action: onCancel) {
|
|
|
|
|
Label("取消", systemImage: "xmark.circle.fill")
|
|
|
|
|
}
|
|
|
|
|
.buttonStyle(.borderedProminent)
|
|
|
|
|
.tint(.red)
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
|
|
|
|
}
|
2024-11-04 17:40:01 +08:00
|
|
|
.padding(.horizontal, 20)
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
|
|
|
|
.padding()
|
2024-11-04 17:40:01 +08:00
|
|
|
.frame(minWidth: 500, minHeight: 300)
|
2024-10-31 22:35:22 +08:00
|
|
|
.background(Color(NSColor.windowBackgroundColor))
|
|
|
|
|
.cornerRadius(8)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview("安装中") {
|
|
|
|
|
InstallProgressView(
|
|
|
|
|
productName: "Adobe Photoshop",
|
|
|
|
|
progress: 0.45,
|
|
|
|
|
status: "正在安装核心组件...",
|
2024-11-01 17:28:23 +08:00
|
|
|
onCancel: {},
|
|
|
|
|
onRetry: nil
|
2024-10-31 22:35:22 +08:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview("准备安装") {
|
|
|
|
|
InstallProgressView(
|
|
|
|
|
productName: "Adobe Photoshop",
|
|
|
|
|
progress: 0.0,
|
|
|
|
|
status: "正在准备安装...",
|
2024-11-01 17:28:23 +08:00
|
|
|
onCancel: {},
|
|
|
|
|
onRetry: nil
|
2024-10-31 22:35:22 +08:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview("安装完成") {
|
|
|
|
|
InstallProgressView(
|
|
|
|
|
productName: "Adobe Photoshop",
|
|
|
|
|
progress: 1.0,
|
|
|
|
|
status: "安装完成",
|
2024-11-01 17:28:23 +08:00
|
|
|
onCancel: {},
|
|
|
|
|
onRetry: nil
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview("安装失败") {
|
|
|
|
|
InstallProgressView(
|
|
|
|
|
productName: "Adobe Photoshop",
|
|
|
|
|
progress: 0.0,
|
|
|
|
|
status: "安装失败: 权限被拒绝",
|
|
|
|
|
onCancel: {},
|
|
|
|
|
onRetry: {}
|
2024-10-31 22:35:22 +08:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview("在深色模式下") {
|
|
|
|
|
InstallProgressView(
|
|
|
|
|
productName: "Adobe Photoshop",
|
|
|
|
|
progress: 0.75,
|
|
|
|
|
status: "正在安装...",
|
2024-11-01 17:28:23 +08:00
|
|
|
onCancel: {},
|
|
|
|
|
onRetry: nil
|
2024-10-31 22:35:22 +08:00
|
|
|
)
|
|
|
|
|
.preferredColorScheme(.dark)
|
2024-11-01 17:28:23 +08:00
|
|
|
}
|
2024-11-04 17:40:01 +08:00
|
|
|
|
|
|
|
|
#Preview("安装中带日志") {
|
|
|
|
|
let networkManager = NetworkManager()
|
|
|
|
|
return InstallProgressView(
|
|
|
|
|
productName: "Adobe Photoshop",
|
|
|
|
|
progress: 0.45,
|
|
|
|
|
status: "正在安装核心组件...",
|
|
|
|
|
onCancel: {},
|
|
|
|
|
onRetry: nil
|
|
|
|
|
)
|
|
|
|
|
.environmentObject(networkManager)
|
|
|
|
|
.onAppear {
|
|
|
|
|
let previewLogs = [
|
|
|
|
|
"正在准备安装...",
|
|
|
|
|
"Progress: 10%",
|
|
|
|
|
"Progress: 20%",
|
|
|
|
|
"Progress: 30%",
|
|
|
|
|
"Progress: 40%",
|
|
|
|
|
"Progress: 45%",
|
|
|
|
|
"正在安装核心组件...",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (index, log) in previewLogs.enumerated() {
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.5) {
|
|
|
|
|
networkManager.installationLogs.append(log)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview("安装失败带日志") {
|
|
|
|
|
let networkManager = NetworkManager()
|
|
|
|
|
return InstallProgressView(
|
|
|
|
|
productName: "Adobe Photoshop",
|
|
|
|
|
progress: 0.0,
|
|
|
|
|
status: "安装失败: 权限被拒绝",
|
|
|
|
|
onCancel: {},
|
|
|
|
|
onRetry: {}
|
|
|
|
|
)
|
|
|
|
|
.environmentObject(networkManager)
|
|
|
|
|
.onAppear {
|
|
|
|
|
let previewLogs = [
|
|
|
|
|
"正在准备安装...",
|
|
|
|
|
"Progress: 10%",
|
|
|
|
|
"Progress: 20%",
|
|
|
|
|
"检查权限...",
|
|
|
|
|
"权限检查失败",
|
|
|
|
|
"安装失败: 权限被拒绝"
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (index, log) in previewLogs.enumerated() {
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.5) {
|
|
|
|
|
networkManager.installationLogs.append(log)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview("安装完成带日志") {
|
|
|
|
|
let networkManager = NetworkManager()
|
|
|
|
|
return InstallProgressView(
|
|
|
|
|
productName: "Adobe Photoshop",
|
|
|
|
|
progress: 1.0,
|
|
|
|
|
status: "安装完成",
|
|
|
|
|
onCancel: {},
|
|
|
|
|
onRetry: nil
|
|
|
|
|
)
|
|
|
|
|
.environmentObject(networkManager)
|
|
|
|
|
.onAppear {
|
|
|
|
|
let previewLogs = [
|
|
|
|
|
"正在准备安装...",
|
|
|
|
|
"Progress: 25%",
|
|
|
|
|
"Progress: 50%",
|
|
|
|
|
"Progress: 75%",
|
|
|
|
|
"Progress: 100%",
|
|
|
|
|
"正在完成安装...",
|
|
|
|
|
"安装完成"
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (index, log) in previewLogs.enumerated() {
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.5) {
|
|
|
|
|
networkManager.installationLogs.append(log)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview("在深色模式下带日志") {
|
|
|
|
|
let networkManager = NetworkManager()
|
|
|
|
|
return InstallProgressView(
|
|
|
|
|
productName: "Adobe Photoshop",
|
|
|
|
|
progress: 0.75,
|
|
|
|
|
status: "正在安装...",
|
|
|
|
|
onCancel: {},
|
|
|
|
|
onRetry: nil
|
|
|
|
|
)
|
|
|
|
|
.environmentObject(networkManager)
|
|
|
|
|
.preferredColorScheme(.dark)
|
|
|
|
|
.onAppear {
|
|
|
|
|
let previewLogs = [
|
|
|
|
|
"正在准备安装...",
|
|
|
|
|
"Progress: 25%",
|
|
|
|
|
"Progress: 50%",
|
|
|
|
|
"Progress: 75%",
|
|
|
|
|
"正在安装..."
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (index, log) in previewLogs.enumerated() {
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.5) {
|
|
|
|
|
networkManager.installationLogs.append(log)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|