2024-10-31 22:35:22 +08:00
|
|
|
//
|
2024-11-05 20:30:18 +08:00
|
|
|
// Adobe Downloader
|
2024-10-31 22:35:22 +08:00
|
|
|
//
|
|
|
|
|
// Created by X1a0He on 2024/10/30.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import SwiftUI
|
2024-11-07 16:14:42 +08:00
|
|
|
import Sparkle
|
2024-11-13 13:20:25 +08:00
|
|
|
import Combine
|
|
|
|
|
|
|
|
|
|
struct PulsingCircle: View {
|
|
|
|
|
let color: Color
|
|
|
|
|
@State private var scale: CGFloat = 1.0
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-13 13:20:25 +08:00
|
|
|
var body: some View {
|
|
|
|
|
Circle()
|
|
|
|
|
.fill(color)
|
|
|
|
|
.frame(width: 8, height: 8)
|
|
|
|
|
.scaleEffect(scale)
|
|
|
|
|
.animation(
|
|
|
|
|
Animation.easeInOut(duration: 1.0)
|
|
|
|
|
.repeatForever(autoreverses: true),
|
|
|
|
|
value: scale
|
|
|
|
|
)
|
|
|
|
|
.onAppear {
|
|
|
|
|
scale = 1.5
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-31 22:35:22 +08:00
|
|
|
|
|
|
|
|
struct AboutView: View {
|
2024-11-07 16:14:42 +08:00
|
|
|
private let updater: SPUUpdater
|
|
|
|
|
|
|
|
|
|
init(updater: SPUUpdater) {
|
|
|
|
|
self.updater = updater
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-10-31 22:35:22 +08:00
|
|
|
var body: some View {
|
|
|
|
|
TabView {
|
2024-11-07 16:14:42 +08:00
|
|
|
GeneralSettingsView(updater: updater)
|
2024-10-31 22:35:22 +08:00
|
|
|
.tabItem {
|
2024-11-06 21:57:17 +08:00
|
|
|
Label("通用", systemImage: "gear")
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
2024-11-11 21:41:47 +08:00
|
|
|
.id("general_settings")
|
2024-11-01 14:34:59 +08:00
|
|
|
|
2024-10-31 22:35:22 +08:00
|
|
|
AboutAppView()
|
|
|
|
|
.tabItem {
|
2024-11-06 21:57:17 +08:00
|
|
|
Label("关于", systemImage: "info.circle")
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
2024-11-11 21:41:47 +08:00
|
|
|
.id("about_app")
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
2024-11-07 16:14:42 +08:00
|
|
|
.background(Color(NSColor.windowBackgroundColor))
|
2024-11-11 19:16:56 +08:00
|
|
|
.frame(width: 600)
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-11 21:41:47 +08:00
|
|
|
final class GeneralSettingsViewModel: ObservableObject {
|
|
|
|
|
@Published var setupVersion: String = ""
|
|
|
|
|
@Published var isDownloadingSetup = false
|
|
|
|
|
@Published var setupDownloadProgress = 0.0
|
|
|
|
|
@Published var setupDownloadStatus = ""
|
|
|
|
|
@Published var showAlert = false
|
|
|
|
|
@Published var alertMessage = ""
|
|
|
|
|
@Published var isSuccess = false
|
|
|
|
|
@Published var showDownloadAlert = false
|
|
|
|
|
@Published var showLanguagePicker = false
|
|
|
|
|
@Published var showDownloadConfirmAlert = false
|
|
|
|
|
@Published var showReprocessConfirmAlert = false
|
2024-11-13 15:54:25 +08:00
|
|
|
@Published var isProcessing = false
|
|
|
|
|
@Published var helperConnectionStatus: HelperConnectionStatus = .connecting
|
|
|
|
|
@Published var downloadAppleSilicon: Bool {
|
|
|
|
|
didSet {
|
|
|
|
|
UserDefaults.standard.set(downloadAppleSilicon, forKey: "downloadAppleSilicon")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-11 21:41:47 +08:00
|
|
|
@AppStorage("defaultLanguage") var defaultLanguage: String = "ALL"
|
|
|
|
|
@AppStorage("defaultDirectory") var defaultDirectory: String = ""
|
|
|
|
|
@AppStorage("useDefaultLanguage") var useDefaultLanguage: Bool = true
|
|
|
|
|
@AppStorage("useDefaultDirectory") var useDefaultDirectory: Bool = true
|
2024-11-13 15:54:25 +08:00
|
|
|
@AppStorage("confirmRedownload") var confirmRedownload: Bool = true
|
|
|
|
|
|
2024-11-11 21:41:47 +08:00
|
|
|
@Published var automaticallyChecksForUpdates: Bool
|
|
|
|
|
@Published var automaticallyDownloadsUpdates: Bool
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-11 21:41:47 +08:00
|
|
|
@Published var isCancelled = false
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-13 13:20:25 +08:00
|
|
|
private var cancellables = Set<AnyCancellable>()
|
2024-11-13 15:54:25 +08:00
|
|
|
let updater: SPUUpdater
|
|
|
|
|
|
2024-11-13 13:20:25 +08:00
|
|
|
enum HelperConnectionStatus {
|
|
|
|
|
case connected
|
|
|
|
|
case connecting
|
|
|
|
|
case disconnected
|
|
|
|
|
case checking
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-11 21:41:47 +08:00
|
|
|
init(updater: SPUUpdater) {
|
|
|
|
|
self.updater = updater
|
|
|
|
|
self.automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates
|
|
|
|
|
self.automaticallyDownloadsUpdates = updater.automaticallyDownloadsUpdates
|
2024-11-13 15:54:25 +08:00
|
|
|
self.downloadAppleSilicon = UserDefaults.standard.bool(forKey: "downloadAppleSilicon")
|
2024-11-13 13:20:25 +08:00
|
|
|
|
|
|
|
|
self.helperConnectionStatus = .connecting
|
|
|
|
|
|
|
|
|
|
PrivilegedHelperManager.shared.$connectionState
|
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
|
.sink { [weak self] state in
|
|
|
|
|
switch state {
|
|
|
|
|
case .connected:
|
|
|
|
|
self?.helperConnectionStatus = .connected
|
|
|
|
|
case .disconnected:
|
|
|
|
|
self?.helperConnectionStatus = .disconnected
|
|
|
|
|
case .connecting:
|
|
|
|
|
self?.helperConnectionStatus = .connecting
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
PrivilegedHelperManager.shared.executeCommand("whoami") { _ in }
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-13 13:20:25 +08:00
|
|
|
deinit {
|
|
|
|
|
cancellables.removeAll()
|
2024-11-11 21:41:47 +08:00
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-11 21:41:47 +08:00
|
|
|
func updateAutomaticallyChecksForUpdates(_ newValue: Bool) {
|
|
|
|
|
automaticallyChecksForUpdates = newValue
|
|
|
|
|
updater.automaticallyChecksForUpdates = newValue
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-11 21:41:47 +08:00
|
|
|
func updateAutomaticallyDownloadsUpdates(_ newValue: Bool) {
|
|
|
|
|
automaticallyDownloadsUpdates = newValue
|
|
|
|
|
updater.automaticallyDownloadsUpdates = newValue
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-11 21:41:47 +08:00
|
|
|
var isAutomaticallyDownloadsUpdatesDisabled: Bool {
|
|
|
|
|
!automaticallyChecksForUpdates
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-11 21:41:47 +08:00
|
|
|
func cancelDownload() {
|
|
|
|
|
isCancelled = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-31 22:35:22 +08:00
|
|
|
struct GeneralSettingsView: View {
|
2024-11-11 21:41:47 +08:00
|
|
|
@StateObject private var viewModel: GeneralSettingsViewModel
|
2024-11-13 13:20:25 +08:00
|
|
|
@State private var showHelperAlert = false
|
|
|
|
|
@State private var helperAlertMessage = ""
|
|
|
|
|
@State private var helperAlertSuccess = false
|
2024-11-13 15:54:25 +08:00
|
|
|
@EnvironmentObject private var networkManager: NetworkManager
|
2024-11-07 16:14:42 +08:00
|
|
|
|
|
|
|
|
init(updater: SPUUpdater) {
|
2024-11-11 21:41:47 +08:00
|
|
|
_viewModel = StateObject(wrappedValue: GeneralSettingsViewModel(updater: updater))
|
2024-11-07 16:14:42 +08:00
|
|
|
}
|
|
|
|
|
|
2024-10-31 22:35:22 +08:00
|
|
|
var body: some View {
|
|
|
|
|
Form {
|
2024-11-13 15:54:25 +08:00
|
|
|
DownloadSettingsView(viewModel: viewModel)
|
|
|
|
|
|
|
|
|
|
OtherSettingsView(viewModel: viewModel,
|
|
|
|
|
showHelperAlert: $showHelperAlert,
|
|
|
|
|
helperAlertMessage: $helperAlertMessage,
|
|
|
|
|
helperAlertSuccess: $helperAlertSuccess)
|
|
|
|
|
|
|
|
|
|
UpdateSettingsView(viewModel: viewModel)
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
|
|
|
|
.padding()
|
2024-11-04 17:40:01 +08:00
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
2024-11-13 15:54:25 +08:00
|
|
|
.alert(helperAlertSuccess ? "操作成功" : "操作失败", isPresented: $showHelperAlert) {
|
|
|
|
|
Button("确定") { }
|
|
|
|
|
} message: {
|
|
|
|
|
Text(helperAlertMessage)
|
2024-11-11 19:16:56 +08:00
|
|
|
}
|
2024-11-11 21:41:47 +08:00
|
|
|
.alert("需要下载 Setup 组件", isPresented: $viewModel.showDownloadAlert) {
|
|
|
|
|
Button("取消", role: .cancel) { }
|
|
|
|
|
Button("下载") {
|
|
|
|
|
Task {
|
|
|
|
|
viewModel.isDownloadingSetup = true
|
|
|
|
|
viewModel.isCancelled = false
|
|
|
|
|
do {
|
|
|
|
|
try await networkManager.downloadUtils.downloadSetupComponents(
|
|
|
|
|
progressHandler: { progress, status in
|
|
|
|
|
viewModel.setupDownloadProgress = progress
|
|
|
|
|
viewModel.setupDownloadStatus = status
|
|
|
|
|
},
|
|
|
|
|
cancellationHandler: { viewModel.isCancelled }
|
|
|
|
|
)
|
|
|
|
|
viewModel.setupVersion = ModifySetup.checkComponentVersion()
|
|
|
|
|
viewModel.isSuccess = true
|
|
|
|
|
viewModel.alertMessage = "Setup 组件安装成功"
|
|
|
|
|
} catch NetworkError.cancelled {
|
|
|
|
|
viewModel.isSuccess = false
|
|
|
|
|
viewModel.alertMessage = "下载已取消"
|
|
|
|
|
} catch {
|
|
|
|
|
viewModel.isSuccess = false
|
|
|
|
|
viewModel.alertMessage = error.localizedDescription
|
|
|
|
|
}
|
|
|
|
|
viewModel.showAlert = true
|
|
|
|
|
viewModel.isDownloadingSetup = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} message: {
|
|
|
|
|
Text("检测到系统中不存在 Setup 组件,需要先下载组件才能继续操作。")
|
|
|
|
|
}
|
|
|
|
|
.alert("确认下载", isPresented: $viewModel.showDownloadConfirmAlert) {
|
|
|
|
|
Button("取消", role: .cancel) { }
|
|
|
|
|
Button("确定") {
|
|
|
|
|
Task {
|
|
|
|
|
viewModel.isDownloadingSetup = true
|
|
|
|
|
viewModel.isCancelled = false
|
|
|
|
|
do {
|
|
|
|
|
try await networkManager.downloadUtils.downloadSetupComponents(
|
|
|
|
|
progressHandler: { progress, status in
|
|
|
|
|
viewModel.setupDownloadProgress = progress
|
|
|
|
|
viewModel.setupDownloadStatus = status
|
|
|
|
|
},
|
|
|
|
|
cancellationHandler: { viewModel.isCancelled }
|
|
|
|
|
)
|
|
|
|
|
viewModel.setupVersion = ModifySetup.checkComponentVersion()
|
|
|
|
|
viewModel.isSuccess = true
|
|
|
|
|
viewModel.alertMessage = "Setup 组件安装成功"
|
|
|
|
|
} catch NetworkError.cancelled {
|
|
|
|
|
viewModel.isSuccess = false
|
|
|
|
|
viewModel.alertMessage = "下载已取消"
|
|
|
|
|
} catch {
|
|
|
|
|
viewModel.isSuccess = false
|
|
|
|
|
viewModel.alertMessage = error.localizedDescription
|
|
|
|
|
}
|
|
|
|
|
viewModel.showAlert = true
|
|
|
|
|
viewModel.isDownloadingSetup = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} message: {
|
2024-11-13 15:54:25 +08:00
|
|
|
Text("确定要下载并安装 Setup 组件吗?")
|
2024-11-11 21:41:47 +08:00
|
|
|
}
|
|
|
|
|
.alert("确认重新处理", isPresented: $viewModel.showReprocessConfirmAlert) {
|
|
|
|
|
Button("取消", role: .cancel) { }
|
|
|
|
|
Button("确定") {
|
2024-11-13 15:54:25 +08:00
|
|
|
viewModel.isProcessing = true
|
|
|
|
|
ModifySetup.backupAndModifySetupFile { success, message in
|
|
|
|
|
viewModel.isProcessing = false
|
2024-11-11 21:41:47 +08:00
|
|
|
viewModel.isSuccess = success
|
|
|
|
|
viewModel.alertMessage = message
|
|
|
|
|
viewModel.showAlert = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} message: {
|
2024-11-13 15:54:25 +08:00
|
|
|
Text("确定要重新处理 Setup 组件吗?")
|
2024-11-11 21:41:47 +08:00
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
.alert(viewModel.isSuccess ? "操作成功" : "操作失败", isPresented: $viewModel.showAlert) {
|
2024-11-13 13:20:25 +08:00
|
|
|
Button("确定") { }
|
|
|
|
|
} message: {
|
2024-11-13 15:54:25 +08:00
|
|
|
Text(viewModel.alertMessage)
|
2024-11-13 13:20:25 +08:00
|
|
|
}
|
2024-11-11 21:41:47 +08:00
|
|
|
.task {
|
|
|
|
|
viewModel.setupVersion = ModifySetup.checkComponentVersion()
|
2024-11-13 15:54:25 +08:00
|
|
|
networkManager.updateAllowedPlatform(useAppleSilicon: viewModel.downloadAppleSilicon)
|
2024-11-06 10:09:16 +08:00
|
|
|
}
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DownloadSettingsView: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
GroupBox(label: Text("下载设置").padding(.bottom, 8)) {
|
|
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
|
|
LanguageSettingRow(viewModel: viewModel)
|
|
|
|
|
Divider()
|
|
|
|
|
DirectorySettingRow(viewModel: viewModel)
|
|
|
|
|
Divider()
|
|
|
|
|
RedownloadConfirmRow(viewModel: viewModel)
|
|
|
|
|
Divider()
|
|
|
|
|
ArchitectureSettingRow(viewModel: viewModel)
|
|
|
|
|
}
|
|
|
|
|
.padding(8)
|
|
|
|
|
}
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct OtherSettingsView: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
@Binding var showHelperAlert: Bool
|
|
|
|
|
@Binding var helperAlertMessage: String
|
|
|
|
|
@Binding var helperAlertSuccess: Bool
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
GroupBox(label: Text("其他设置").padding(.bottom, 8)) {
|
|
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
|
|
HelperStatusRow(viewModel: viewModel, showHelperAlert: $showHelperAlert,
|
|
|
|
|
helperAlertMessage: $helperAlertMessage,
|
|
|
|
|
helperAlertSuccess: $helperAlertSuccess)
|
|
|
|
|
Divider()
|
|
|
|
|
SetupComponentRow(viewModel: viewModel)
|
|
|
|
|
}
|
|
|
|
|
.padding(8)
|
|
|
|
|
}
|
2024-11-01 13:19:01 +08:00
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct UpdateSettingsView: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
GroupBox(label: Text("更新设置").padding(.bottom, 8)) {
|
|
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
|
|
AutoUpdateRow(viewModel: viewModel)
|
|
|
|
|
Divider()
|
|
|
|
|
AutoDownloadRow(viewModel: viewModel)
|
|
|
|
|
}
|
|
|
|
|
.padding(8)
|
2024-11-01 13:19:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AboutAppView: View {
|
2024-11-07 16:14:42 +08:00
|
|
|
private var appVersion: String {
|
|
|
|
|
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
|
|
|
|
|
// let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
|
|
|
|
|
// return "Version \(version) (\(build))"
|
|
|
|
|
return "\(version)"
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-10-31 22:35:22 +08:00
|
|
|
var body: some View {
|
|
|
|
|
VStack(spacing: 12) {
|
|
|
|
|
Image(nsImage: NSApp.applicationIconImage)
|
|
|
|
|
.resizable()
|
|
|
|
|
.frame(width: 96, height: 96)
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-07 16:14:42 +08:00
|
|
|
Text("Adobe Downloader \(appVersion)")
|
2024-10-31 22:35:22 +08:00
|
|
|
.font(.title2)
|
|
|
|
|
.bold()
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-10-31 22:35:22 +08:00
|
|
|
Text("By X1a0He. ❤️ Love from China. ❤️")
|
|
|
|
|
.font(.subheadline)
|
|
|
|
|
.foregroundColor(.secondary)
|
2024-11-01 14:34:59 +08:00
|
|
|
|
2024-11-06 21:57:17 +08:00
|
|
|
Link("联系 @X1a0He",
|
2024-11-06 10:09:16 +08:00
|
|
|
destination: URL(string: "https://t.me/X1a0He")!)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(.blue)
|
2024-11-01 14:34:59 +08:00
|
|
|
Link("Github: Adobe Downloader",
|
|
|
|
|
destination: URL(string: "https://github.com/X1a0He/Adobe-Downloader")!)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(.blue)
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-06 21:57:17 +08:00
|
|
|
Link("感谢 Drovosek01: adobe-packager",
|
2024-11-01 14:34:59 +08:00
|
|
|
destination: URL(string: "https://github.com/Drovosek01/adobe-packager")!)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(.blue)
|
2024-11-05 20:30:18 +08:00
|
|
|
|
2024-11-06 21:57:17 +08:00
|
|
|
Link("感谢 QiuChenly: InjectLib",
|
2024-11-05 20:30:18 +08:00
|
|
|
destination: URL(string: "https://github.com/QiuChenly/InjectLib")!)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(.blue)
|
|
|
|
|
|
2024-11-06 21:57:17 +08:00
|
|
|
Text("GNU通用公共许可证GPL v3.")
|
2024-10-31 22:35:22 +08:00
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
}
|
|
|
|
|
.padding()
|
2024-11-04 17:40:01 +08:00
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
2024-10-31 22:35:22 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-07 16:14:42 +08:00
|
|
|
#Preview("About Tab") {
|
|
|
|
|
AboutAppView()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview("General Settings") {
|
2024-11-06 10:09:16 +08:00
|
|
|
let networkManager = NetworkManager()
|
2024-11-07 16:14:42 +08:00
|
|
|
VStack {
|
|
|
|
|
GeneralSettingsView(updater: PreviewUpdater())
|
|
|
|
|
.environmentObject(networkManager)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class PreviewUpdater: SPUUpdater {
|
|
|
|
|
init() {
|
|
|
|
|
let hostBundle = Bundle.main
|
|
|
|
|
let applicationBundle = Bundle.main
|
|
|
|
|
let userDriver = SPUStandardUserDriver(hostBundle: hostBundle, delegate: nil)
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-07 16:14:42 +08:00
|
|
|
super.init(
|
|
|
|
|
hostBundle: hostBundle,
|
|
|
|
|
applicationBundle: applicationBundle,
|
|
|
|
|
userDriver: userDriver,
|
|
|
|
|
delegate: nil
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-07 16:14:42 +08:00
|
|
|
override var automaticallyChecksForUpdates: Bool {
|
|
|
|
|
get { true }
|
|
|
|
|
set { }
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
|
2024-11-07 16:14:42 +08:00
|
|
|
override var automaticallyDownloadsUpdates: Bool {
|
|
|
|
|
get { true }
|
|
|
|
|
set { }
|
|
|
|
|
}
|
2024-11-13 15:54:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct LanguageSettingRow: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
HStack {
|
|
|
|
|
Toggle("使用默认语言", isOn: $viewModel.useDefaultLanguage)
|
|
|
|
|
.padding(.leading, 5)
|
|
|
|
|
Spacer()
|
|
|
|
|
Text(getLanguageName(code: viewModel.defaultLanguage))
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
Button("选择") {
|
|
|
|
|
viewModel.showLanguagePicker = true
|
|
|
|
|
}
|
|
|
|
|
.padding(.trailing, 5)
|
|
|
|
|
}
|
|
|
|
|
.sheet(isPresented: $viewModel.showLanguagePicker) {
|
|
|
|
|
LanguagePickerView(languages: AppStatics.supportedLanguages) { language in
|
|
|
|
|
viewModel.defaultLanguage = language
|
|
|
|
|
viewModel.showLanguagePicker = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLanguageName(code: String) -> String {
|
|
|
|
|
AppStatics.supportedLanguages.first { $0.code == code }?.name ?? code
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DirectorySettingRow: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
HStack {
|
|
|
|
|
Toggle("使用默认目录", isOn: $viewModel.useDefaultDirectory)
|
|
|
|
|
.padding(.leading, 5)
|
|
|
|
|
Spacer()
|
|
|
|
|
Text(formatPath(viewModel.defaultDirectory))
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
.lineLimit(1)
|
|
|
|
|
.truncationMode(.middle)
|
|
|
|
|
Button("选择") {
|
|
|
|
|
selectDirectory()
|
|
|
|
|
}
|
|
|
|
|
.padding(.trailing, 5)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func formatPath(_ path: String) -> String {
|
|
|
|
|
if path.isEmpty { return String(localized: "未设置") }
|
|
|
|
|
return URL(fileURLWithPath: path).lastPathComponent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func selectDirectory() {
|
|
|
|
|
let panel = NSOpenPanel()
|
|
|
|
|
panel.title = "选择默认下载目录"
|
|
|
|
|
panel.canCreateDirectories = true
|
|
|
|
|
panel.canChooseDirectories = true
|
|
|
|
|
panel.canChooseFiles = false
|
|
|
|
|
|
|
|
|
|
if panel.runModal() == .OK {
|
|
|
|
|
viewModel.defaultDirectory = panel.url?.path ?? ""
|
|
|
|
|
viewModel.useDefaultDirectory = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct RedownloadConfirmRow: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
HStack {
|
|
|
|
|
Toggle("重新下载时需要确认", isOn: $viewModel.confirmRedownload)
|
|
|
|
|
.padding(.leading, 5)
|
|
|
|
|
Spacer()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct ArchitectureSettingRow: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
@EnvironmentObject private var networkManager: NetworkManager
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
HStack {
|
|
|
|
|
Toggle("下载 Apple Silicon 架构", isOn: $viewModel.downloadAppleSilicon)
|
|
|
|
|
.padding(.leading, 5)
|
|
|
|
|
Spacer()
|
|
|
|
|
Text("当前架构: \(AppStatics.cpuArchitecture)")
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
.lineLimit(1)
|
|
|
|
|
.truncationMode(.middle)
|
|
|
|
|
}
|
|
|
|
|
.onChange(of: viewModel.downloadAppleSilicon) { newValue in
|
|
|
|
|
networkManager.updateAllowedPlatform(useAppleSilicon: newValue)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct HelperStatusRow: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
@Binding var showHelperAlert: Bool
|
|
|
|
|
@Binding var helperAlertMessage: String
|
|
|
|
|
@Binding var helperAlertSuccess: Bool
|
|
|
|
|
@State private var isReinstallingHelper = false
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
|
|
|
HStack {
|
|
|
|
|
Text("Helper 安装状态: ")
|
|
|
|
|
if PrivilegedHelperManager.getHelperStatus {
|
|
|
|
|
Image(systemName: "checkmark.circle.fill")
|
|
|
|
|
.foregroundColor(.green)
|
|
|
|
|
Text("已安装")
|
|
|
|
|
} else {
|
|
|
|
|
Image(systemName: "xmark.circle.fill")
|
|
|
|
|
.foregroundColor(.red)
|
|
|
|
|
Text("未安装")
|
|
|
|
|
.foregroundColor(.red)
|
|
|
|
|
}
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
if isReinstallingHelper {
|
|
|
|
|
ProgressView()
|
|
|
|
|
.scaleEffect(0.7)
|
|
|
|
|
.frame(width: 16, height: 16)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
|
|
|
|
isReinstallingHelper = true
|
|
|
|
|
PrivilegedHelperManager.shared.reinstallHelper { success, message in
|
|
|
|
|
helperAlertSuccess = success
|
|
|
|
|
helperAlertMessage = message
|
|
|
|
|
showHelperAlert = true
|
|
|
|
|
isReinstallingHelper = false
|
|
|
|
|
}
|
|
|
|
|
}) {
|
|
|
|
|
Text("重新安装")
|
|
|
|
|
}
|
|
|
|
|
.disabled(isReinstallingHelper)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !PrivilegedHelperManager.getHelperStatus {
|
|
|
|
|
Text("Helper未安装将导致无法执行需要管理员权限的操作")
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(.red)
|
|
|
|
|
}
|
|
|
|
|
Divider()
|
|
|
|
|
HStack {
|
|
|
|
|
Text("Helper 当前状态: ")
|
|
|
|
|
PulsingCircle(color: helperStatusColor)
|
|
|
|
|
.padding(.horizontal, 4)
|
|
|
|
|
Text(helperStatusText)
|
|
|
|
|
.foregroundColor(helperStatusColor)
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
|
|
|
|
PrivilegedHelperManager.shared.reconnectHelper { success, message in
|
|
|
|
|
helperAlertSuccess = success
|
|
|
|
|
helperAlertMessage = message
|
|
|
|
|
showHelperAlert = true
|
|
|
|
|
}
|
|
|
|
|
}) {
|
|
|
|
|
Text("重新连接Helper")
|
|
|
|
|
}
|
|
|
|
|
.disabled(isReinstallingHelper)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var helperStatusColor: Color {
|
|
|
|
|
switch viewModel.helperConnectionStatus {
|
|
|
|
|
case .connected: return .green
|
|
|
|
|
case .connecting, .checking: return .orange
|
|
|
|
|
case .disconnected: return .red
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var helperStatusText: String {
|
|
|
|
|
switch viewModel.helperConnectionStatus {
|
|
|
|
|
case .connected: return "运行正常"
|
|
|
|
|
case .connecting: return "正在连接"
|
|
|
|
|
case .checking: return "检查中"
|
|
|
|
|
case .disconnected: return "连接断开"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct SetupComponentRow: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
|
|
|
HStack {
|
|
|
|
|
Text("Setup 组件状态: ")
|
|
|
|
|
if ModifySetup.isSetupBackup() {
|
|
|
|
|
Image(systemName: "checkmark.circle.fill")
|
|
|
|
|
.foregroundColor(.green)
|
|
|
|
|
Text("已备份处理")
|
|
|
|
|
} else {
|
|
|
|
|
Image(systemName: "xmark.circle.fill")
|
|
|
|
|
.foregroundColor(.red)
|
|
|
|
|
Text("(将导致无法使用安装功能)")
|
|
|
|
|
}
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
|
|
|
|
if !ModifySetup.isSetupExists() {
|
|
|
|
|
viewModel.showDownloadAlert = true
|
|
|
|
|
} else {
|
|
|
|
|
viewModel.showReprocessConfirmAlert = true
|
|
|
|
|
}
|
|
|
|
|
}) {
|
|
|
|
|
Text("重新处理")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Divider()
|
|
|
|
|
HStack {
|
|
|
|
|
Text("Setup 组件版本: \(viewModel.setupVersion)")
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
if viewModel.isDownloadingSetup {
|
|
|
|
|
ProgressView(value: viewModel.setupDownloadProgress) {
|
|
|
|
|
Text(viewModel.setupDownloadStatus)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
}
|
|
|
|
|
.frame(width: 150)
|
|
|
|
|
Button("取消") {
|
|
|
|
|
viewModel.cancelDownload()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Button(action: {
|
|
|
|
|
viewModel.showDownloadConfirmAlert = true
|
|
|
|
|
}) {
|
|
|
|
|
Text("从 GitHub 下载 Setup 组件")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AutoUpdateRow: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
HStack {
|
|
|
|
|
Toggle("自动检查更新版本", isOn: Binding(
|
|
|
|
|
get: { viewModel.automaticallyChecksForUpdates },
|
|
|
|
|
set: { viewModel.updateAutomaticallyChecksForUpdates($0) }
|
|
|
|
|
))
|
|
|
|
|
Spacer()
|
|
|
|
|
CheckForUpdatesView(updater: viewModel.updater)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AutoDownloadRow: View {
|
|
|
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
Toggle("自动下载最新版本", isOn: Binding(
|
|
|
|
|
get: { viewModel.automaticallyDownloadsUpdates },
|
|
|
|
|
set: { viewModel.updateAutomaticallyDownloadsUpdates($0) }
|
|
|
|
|
))
|
|
|
|
|
.disabled(viewModel.isAutomaticallyDownloadsUpdatesDisabled)
|
|
|
|
|
}
|
|
|
|
|
}
|