mirror of
https://github.com/X1a0He/Adobe-Downloader.git
synced 2025-11-25 03:14:57 +08:00
1268 lines
45 KiB
Swift
1268 lines
45 KiB
Swift
//
|
|
// Adobe Downloader
|
|
//
|
|
// Created by X1a0He on 2024/10/30.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Sparkle
|
|
import Combine
|
|
|
|
|
|
private enum AboutViewConstants {
|
|
static let appIconSize: CGFloat = 96
|
|
static let titleFontSize: CGFloat = 18
|
|
static let subtitleFontSize: CGFloat = 14
|
|
static let linkFontSize: CGFloat = 14
|
|
static let licenseFontSize: CGFloat = 12
|
|
|
|
static let verticalSpacing: CGFloat = 12
|
|
static let formPadding: CGFloat = 8
|
|
|
|
static let links: [(title: String, url: String)] = [
|
|
("@X1a0He", "https://t.me/X1a0He_bot"),
|
|
("Github: Adobe Downloader", "https://github.com/X1a0He/Adobe-Downloader"),
|
|
("QiuChenly: InjectLib", "https://github.com/QiuChenly/InjectLib")
|
|
]
|
|
}
|
|
|
|
struct ExternalLinkView: View {
|
|
let title: String
|
|
let url: String
|
|
|
|
var body: some View {
|
|
Link(title, destination: URL(string: url)!)
|
|
.font(.system(size: AboutViewConstants.linkFontSize))
|
|
.foregroundColor(.blue)
|
|
}
|
|
}
|
|
|
|
struct AboutView: View {
|
|
private let updater: SPUUpdater
|
|
@State private var selectedTab = "general_settings"
|
|
|
|
init(updater: SPUUpdater) {
|
|
self.updater = updater
|
|
}
|
|
|
|
var body: some View {
|
|
TabView(selection: $selectedTab) {
|
|
GeneralSettingsView(updater: updater)
|
|
.tabItem {
|
|
Label("通用", systemImage: "gear")
|
|
}
|
|
.tag("general_settings")
|
|
|
|
CleanupView()
|
|
.tabItem {
|
|
Label("清理工具", systemImage: "trash")
|
|
}
|
|
.tag("cleanup_view")
|
|
|
|
QAView()
|
|
.tabItem {
|
|
Label("常见问题", systemImage: "questionmark.circle")
|
|
}
|
|
.tag("qa_view")
|
|
|
|
AboutAppView()
|
|
.tabItem {
|
|
Label("关于", systemImage: "info.circle")
|
|
}
|
|
.tag("about_app")
|
|
}
|
|
.background(Color(.clear))
|
|
.frame(width: 600)
|
|
.onAppear {
|
|
selectedTab = "general_settings"
|
|
}
|
|
}
|
|
}
|
|
|
|
struct AboutAppView: View {
|
|
private var appVersion: String {
|
|
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(spacing: AboutViewConstants.verticalSpacing) {
|
|
appIconSection
|
|
appInfoSection
|
|
linksSection
|
|
licenseSection
|
|
}
|
|
.padding()
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
}
|
|
|
|
private var appIconSection: some View {
|
|
Image(nsImage: NSApp.applicationIconImage)
|
|
.resizable()
|
|
.frame(width: AboutViewConstants.appIconSize, height: AboutViewConstants.appIconSize)
|
|
}
|
|
|
|
private var appInfoSection: some View {
|
|
Group {
|
|
Text("Adobe Downloader \(appVersion)")
|
|
.font(.system(size: AboutViewConstants.titleFontSize))
|
|
.bold()
|
|
|
|
Text("By X1a0He. ❤️ Love from China. 🇨🇳")
|
|
.font(.system(size: AboutViewConstants.subtitleFontSize))
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
|
|
private var linksSection: some View {
|
|
ForEach(AboutViewConstants.links, id: \.url) { link in
|
|
ExternalLinkView(title: link.title, url: link.url)
|
|
}
|
|
}
|
|
|
|
private var licenseSection: some View {
|
|
Text("GNU通用公共许可证GPL v3.")
|
|
.font(.system(size: AboutViewConstants.licenseFontSize))
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
|
|
struct PulsingCircle: View {
|
|
let color: Color
|
|
@State private var pulsing = false
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
ZStack(alignment: .center) {
|
|
Circle()
|
|
.fill(color)
|
|
.frame(width: 6, height: 6)
|
|
|
|
Circle()
|
|
.stroke(color, lineWidth: 1)
|
|
.frame(width: pulsing ? 14 : 8, height: pulsing ? 14 : 8)
|
|
.opacity(pulsing ? 0 : 0.8)
|
|
|
|
Circle()
|
|
.stroke(color, lineWidth: 1)
|
|
.frame(width: pulsing ? 12 : 6, height: pulsing ? 12 : 6)
|
|
.opacity(pulsing ? 0.2 : 0.6)
|
|
}
|
|
.frame(width: 16, height: 16)
|
|
}
|
|
.frame(width: 16, height: 16, alignment: .center)
|
|
.clipped()
|
|
.onAppear {
|
|
withAnimation(Animation.easeOut(duration: 1.5).repeatForever(autoreverses: false)) {
|
|
pulsing = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
@Published var showDownloadOnlyConfirmAlert = false
|
|
@Published var isProcessing = false
|
|
@Published var helperConnectionStatus: HelperConnectionStatus = .disconnected
|
|
@Published var downloadAppleSilicon: Bool {
|
|
didSet {
|
|
StorageData.shared.downloadAppleSilicon = downloadAppleSilicon
|
|
}
|
|
}
|
|
|
|
var defaultLanguage: String {
|
|
get { StorageData.shared.defaultLanguage }
|
|
set { StorageData.shared.defaultLanguage = newValue }
|
|
}
|
|
|
|
var defaultDirectory: String {
|
|
get { StorageData.shared.defaultDirectory }
|
|
set { StorageData.shared.defaultDirectory = newValue }
|
|
}
|
|
|
|
var useDefaultLanguage: Bool {
|
|
get { StorageData.shared.useDefaultLanguage }
|
|
set { StorageData.shared.useDefaultLanguage = newValue }
|
|
}
|
|
|
|
var useDefaultDirectory: Bool {
|
|
get { StorageData.shared.useDefaultDirectory }
|
|
set { StorageData.shared.useDefaultDirectory = newValue }
|
|
}
|
|
|
|
var confirmRedownload: Bool {
|
|
get { StorageData.shared.confirmRedownload }
|
|
set {
|
|
StorageData.shared.confirmRedownload = newValue
|
|
objectWillChange.send()
|
|
}
|
|
}
|
|
|
|
@Published var automaticallyChecksForUpdates: Bool
|
|
@Published var automaticallyDownloadsUpdates: Bool
|
|
|
|
@Published var isCancelled = false
|
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
let updater: SPUUpdater
|
|
|
|
enum HelperConnectionStatus {
|
|
case connected
|
|
case connecting
|
|
case disconnected
|
|
case checking
|
|
}
|
|
|
|
init(updater: SPUUpdater) {
|
|
self.updater = updater
|
|
self.automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates
|
|
self.automaticallyDownloadsUpdates = updater.automaticallyDownloadsUpdates
|
|
self.downloadAppleSilicon = StorageData.shared.downloadAppleSilicon
|
|
|
|
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)
|
|
|
|
NotificationCenter.default.publisher(for: .storageDidChange)
|
|
.receive(on: RunLoop.main)
|
|
.sink { [weak self] _ in
|
|
self?.objectWillChange.send()
|
|
}
|
|
.store(in: &cancellables)
|
|
}
|
|
|
|
deinit {
|
|
cancellables.removeAll()
|
|
}
|
|
|
|
func updateAutomaticallyChecksForUpdates(_ newValue: Bool) {
|
|
automaticallyChecksForUpdates = newValue
|
|
updater.automaticallyChecksForUpdates = newValue
|
|
}
|
|
|
|
func updateAutomaticallyDownloadsUpdates(_ newValue: Bool) {
|
|
automaticallyDownloadsUpdates = newValue
|
|
updater.automaticallyDownloadsUpdates = newValue
|
|
}
|
|
|
|
var isAutomaticallyDownloadsUpdatesDisabled: Bool {
|
|
!automaticallyChecksForUpdates
|
|
}
|
|
|
|
func cancelDownload() {
|
|
isCancelled = true
|
|
}
|
|
}
|
|
|
|
struct GeneralSettingsView: View {
|
|
@StateObject private var viewModel: GeneralSettingsViewModel
|
|
@State private var showHelperAlert = false
|
|
@State private var helperAlertMessage = ""
|
|
@State private var helperAlertSuccess = false
|
|
@EnvironmentObject private var networkManager: NetworkManager
|
|
|
|
init(updater: SPUUpdater) {
|
|
_viewModel = StateObject(wrappedValue: GeneralSettingsViewModel(updater: updater))
|
|
}
|
|
|
|
var body: some View {
|
|
GeneralSettingsContent(
|
|
viewModel: viewModel,
|
|
showHelperAlert: $showHelperAlert,
|
|
helperAlertMessage: $helperAlertMessage,
|
|
helperAlertSuccess: $helperAlertSuccess
|
|
)
|
|
}
|
|
}
|
|
|
|
private struct GeneralSettingsContent: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
@Binding var showHelperAlert: Bool
|
|
@Binding var helperAlertMessage: String
|
|
@Binding var helperAlertSuccess: Bool
|
|
|
|
var body: some View {
|
|
Form {
|
|
DownloadSettingsView(viewModel: viewModel)
|
|
.padding(.bottom, 8)
|
|
HelperSettingsView(viewModel: viewModel,
|
|
showHelperAlert: $showHelperAlert,
|
|
helperAlertMessage: $helperAlertMessage,
|
|
helperAlertSuccess: $helperAlertSuccess)
|
|
.padding(.bottom, 8)
|
|
CCSettingsView(viewModel: viewModel)
|
|
.padding(.bottom, 8)
|
|
UpdateSettingsView(viewModel: viewModel)
|
|
.padding(.bottom, 8)
|
|
CleanConfigView()
|
|
}
|
|
.padding()
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
.modifier(GeneralSettingsAlerts(
|
|
viewModel: viewModel,
|
|
showHelperAlert: $showHelperAlert,
|
|
helperAlertMessage: $helperAlertMessage,
|
|
helperAlertSuccess: $helperAlertSuccess
|
|
))
|
|
.task {
|
|
viewModel.setupVersion = ModifySetup.checkComponentVersion()
|
|
}
|
|
.onReceive(NotificationCenter.default.publisher(for: .storageDidChange)) { _ in
|
|
viewModel.objectWillChange.send()
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct GeneralSettingsAlerts: ViewModifier {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
@Binding var showHelperAlert: Bool
|
|
@Binding var helperAlertMessage: String
|
|
@Binding var helperAlertSuccess: Bool
|
|
@EnvironmentObject private var networkManager: NetworkManager
|
|
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.alert(helperAlertSuccess ? "操作成功" : "操作失败", isPresented: $showHelperAlert) {
|
|
Button("确定") { }
|
|
} message: {
|
|
Text(helperAlertMessage)
|
|
}
|
|
.alert("需要下载 Setup 组件", isPresented: $viewModel.showDownloadAlert) {
|
|
Button("取消", role: .cancel) { }
|
|
Button("下载") {
|
|
Task {
|
|
startDownloadSetup(shouldProcess: false)
|
|
}
|
|
}
|
|
} message: {
|
|
Text("检测到系统中不存在 Setup 组件,需要先下载组件才能继续操作。")
|
|
}
|
|
.alert("确认下载并处理", isPresented: $viewModel.showDownloadConfirmAlert) {
|
|
Button("取消", role: .cancel) { }
|
|
Button("确定") {
|
|
Task {
|
|
startDownloadSetup(shouldProcess: true)
|
|
}
|
|
}
|
|
} message: {
|
|
Text("确定要下载并处理 X1a0He CC 吗?这将完成下载并自动对 Setup 组件进行处理")
|
|
}
|
|
.alert("确认处理", isPresented: $viewModel.showReprocessConfirmAlert) {
|
|
Button("取消", role: .cancel) { }
|
|
Button("确定") {
|
|
Task {
|
|
viewModel.isProcessing = true
|
|
ModifySetup.backupAndModifySetupFile { success, message in
|
|
viewModel.setupVersion = ModifySetup.checkComponentVersion()
|
|
viewModel.isSuccess = success
|
|
viewModel.alertMessage = success ? "Setup 组件处理成功" : "处理失败: \(message)"
|
|
viewModel.showAlert = true
|
|
viewModel.isProcessing = false
|
|
}
|
|
}
|
|
}
|
|
} message: {
|
|
Text("确定要重新处理 Setup 组件吗?这将对 Setup 组件进行修改以启用安装功能。")
|
|
}
|
|
.alert(viewModel.isSuccess ? "操作成功" : "操作失败", isPresented: $viewModel.showAlert) {
|
|
Button("确定") { }
|
|
} message: {
|
|
Text(viewModel.alertMessage)
|
|
}
|
|
.alert("确认下载", isPresented: $viewModel.showDownloadOnlyConfirmAlert) {
|
|
Button("取消", role: .cancel) { }
|
|
Button("确定") {
|
|
Task {
|
|
startDownloadSetup(shouldProcess: false)
|
|
}
|
|
}
|
|
} message: {
|
|
Text("确定要下载 X1a0He CC 吗?下载完成后需要手动处理。")
|
|
}
|
|
}
|
|
|
|
private func startDownloadSetup(shouldProcess: Bool) {
|
|
viewModel.isDownloadingSetup = true
|
|
viewModel.isCancelled = false
|
|
|
|
Task {
|
|
do {
|
|
try await globalNewDownloadUtils.downloadX1a0HeCCPackages(
|
|
progressHandler: { progress, status in
|
|
viewModel.setupDownloadProgress = progress
|
|
viewModel.setupDownloadStatus = status
|
|
},
|
|
cancellationHandler: { viewModel.isCancelled },
|
|
shouldProcess: shouldProcess
|
|
)
|
|
viewModel.setupVersion = ModifySetup.checkComponentVersion()
|
|
viewModel.isSuccess = true
|
|
viewModel.alertMessage = String(localized: shouldProcess ?
|
|
"X1a0He CC 下载并处理成功" :
|
|
"X1a0He CC 下载成功")
|
|
} catch NetworkError.cancelled {
|
|
viewModel.isSuccess = false
|
|
viewModel.alertMessage = String(localized: "下载已取消")
|
|
} catch {
|
|
viewModel.isSuccess = false
|
|
viewModel.alertMessage = error.localizedDescription
|
|
}
|
|
|
|
viewModel.showAlert = true
|
|
viewModel.isDownloadingSetup = false
|
|
}
|
|
}
|
|
}
|
|
|
|
struct DownloadSettingsView: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
var body: some View {
|
|
BeautifulGroupBox(label: {
|
|
Text("下载设置")
|
|
}) {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
LanguageSettingRow(viewModel: viewModel)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
Divider()
|
|
|
|
DirectorySettingRow(viewModel: viewModel)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
Divider()
|
|
|
|
RedownloadConfirmRow(viewModel: viewModel)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
HStack {
|
|
Spacer()
|
|
Rectangle()
|
|
.fill(Color.secondary.opacity(0.15))
|
|
.frame(height: 1)
|
|
Spacer()
|
|
}
|
|
|
|
ArchitectureSettingRow(viewModel: viewModel)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct HelperSettingsView: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
@Binding var showHelperAlert: Bool
|
|
@Binding var helperAlertMessage: String
|
|
@Binding var helperAlertSuccess: Bool
|
|
|
|
var body: some View {
|
|
BeautifulGroupBox(label: {
|
|
Text("Helper 设置")
|
|
}) {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
HelperStatusRow(viewModel: viewModel, showHelperAlert: $showHelperAlert,
|
|
helperAlertMessage: $helperAlertMessage,
|
|
helperAlertSuccess: $helperAlertSuccess)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct CCSettingsView: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
var body: some View {
|
|
BeautifulGroupBox(label: {
|
|
Text("X1a0He CC设置")
|
|
}) {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
SetupComponentRow(viewModel: viewModel)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct UpdateSettingsView: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
private var appVersion: String {
|
|
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
|
|
}
|
|
private var buildVersion: String {
|
|
Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
|
|
}
|
|
|
|
var body: some View {
|
|
BeautifulGroupBox(label: {
|
|
Text("更新设置")
|
|
}) {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
HStack {
|
|
Text("当前版本: ")
|
|
.font(.system(size: 14, weight: .medium))
|
|
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "number.circle.fill")
|
|
.foregroundColor(.blue)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text(appVersion)
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
Text("(\(buildVersion))")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.blue.opacity(0.1))
|
|
.cornerRadius(5)
|
|
}
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.padding(.vertical, 2)
|
|
|
|
Divider()
|
|
|
|
AutoUpdateRow(viewModel: viewModel)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
Divider()
|
|
|
|
AutoDownloadRow(viewModel: viewModel)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private class PreviewUpdater: SPUUpdater {
|
|
init() {
|
|
let hostBundle = Bundle.main
|
|
let applicationBundle = Bundle.main
|
|
let userDriver = SPUStandardUserDriver(hostBundle: hostBundle, delegate: nil)
|
|
|
|
super.init(
|
|
hostBundle: hostBundle,
|
|
applicationBundle: applicationBundle,
|
|
userDriver: userDriver,
|
|
delegate: nil
|
|
)
|
|
}
|
|
|
|
override var automaticallyChecksForUpdates: Bool {
|
|
get { true }
|
|
set { }
|
|
}
|
|
|
|
override var automaticallyDownloadsUpdates: Bool {
|
|
get { true }
|
|
set { }
|
|
}
|
|
}
|
|
|
|
struct LanguageSettingRow: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
var body: some View {
|
|
HStack(spacing: 10) {
|
|
Toggle("使用默认语言", isOn: Binding(
|
|
get: { viewModel.useDefaultLanguage },
|
|
set: { viewModel.useDefaultLanguage = $0 }
|
|
))
|
|
.toggleStyle(SwitchToggleStyle(tint: Color.green))
|
|
.padding(.leading, 5)
|
|
.controlSize(.small)
|
|
.labelsHidden()
|
|
|
|
Text("使用默认语言")
|
|
.font(.system(size: 14))
|
|
|
|
Spacer()
|
|
|
|
if viewModel.useDefaultLanguage {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "globe")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 12))
|
|
Text(getLanguageName(code: viewModel.defaultLanguage))
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.secondary.opacity(0.1))
|
|
.cornerRadius(5)
|
|
|
|
Button(action: {
|
|
viewModel.showLanguagePicker = true
|
|
}) {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "pencil")
|
|
.font(.system(size: 10))
|
|
Text("选择")
|
|
.font(.system(size: 13))
|
|
}
|
|
.frame(minWidth: 60)
|
|
}
|
|
.buttonStyle(BeautifulButtonStyle(baseColor: Color.blue.opacity(0.8)))
|
|
.foregroundColor(.white)
|
|
.padding(.trailing, 5)
|
|
.disabled(!viewModel.useDefaultLanguage)
|
|
}
|
|
}
|
|
.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(spacing: 10) {
|
|
Toggle("使用默认目录", isOn: $viewModel.useDefaultDirectory)
|
|
.toggleStyle(SwitchToggleStyle(tint: Color.green))
|
|
.padding(.leading, 5)
|
|
.controlSize(.small)
|
|
.labelsHidden()
|
|
|
|
Text("使用默认目录")
|
|
.font(.system(size: 14))
|
|
|
|
Spacer()
|
|
|
|
if viewModel.useDefaultDirectory {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "folder")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16)
|
|
Text(formatPath(viewModel.defaultDirectory))
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
.lineLimit(1)
|
|
.truncationMode(.middle)
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.secondary.opacity(0.1))
|
|
.cornerRadius(5)
|
|
|
|
Button(action: {
|
|
selectDirectory()
|
|
}) {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "folder.badge.plus")
|
|
.font(.system(size: 10))
|
|
Text("选择")
|
|
.font(.system(size: 13))
|
|
}
|
|
.frame(minWidth: 60)
|
|
}
|
|
.buttonStyle(BeautifulButtonStyle(baseColor: Color.blue.opacity(0.8)))
|
|
.foregroundColor(.white)
|
|
.padding(.trailing, 5)
|
|
.disabled(!viewModel.useDefaultDirectory)
|
|
}
|
|
}
|
|
}
|
|
|
|
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(spacing: 10) {
|
|
Toggle("重新下载时需要确认", isOn: $viewModel.confirmRedownload)
|
|
.toggleStyle(SwitchToggleStyle(tint: Color.green))
|
|
.padding(.leading, 5)
|
|
.controlSize(.small)
|
|
.labelsHidden()
|
|
|
|
Text("重新下载时需要确认")
|
|
.font(.system(size: 14))
|
|
|
|
Spacer()
|
|
|
|
if viewModel.confirmRedownload {
|
|
HStack(spacing: 5) {
|
|
Image(systemName: "exclamationmark.triangle")
|
|
.foregroundColor(.orange)
|
|
.font(.system(size: 12))
|
|
Text("已启用确认")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.orange.opacity(0.1))
|
|
.cornerRadius(5)
|
|
.padding(.trailing, 5)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ArchitectureSettingRow: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
@ObservedObject private var networkManager = globalNetworkManager
|
|
|
|
var body: some View {
|
|
HStack(spacing: 10) {
|
|
Toggle("下载 Apple Silicon 架构", isOn: $viewModel.downloadAppleSilicon)
|
|
.toggleStyle(SwitchToggleStyle(tint: Color.green))
|
|
.padding(.leading, 5)
|
|
.controlSize(.small)
|
|
.disabled(networkManager.loadingState == .loading)
|
|
.labelsHidden()
|
|
|
|
Text("下载 Apple Silicon 架构")
|
|
.font(.system(size: 14))
|
|
|
|
Spacer()
|
|
|
|
HStack(spacing: 5) {
|
|
Image(systemName: "cpu")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 12))
|
|
Text("当前架构: \(AppStatics.cpuArchitecture)")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
.lineLimit(1)
|
|
.truncationMode(.middle)
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.secondary.opacity(0.1))
|
|
.cornerRadius(5)
|
|
.padding(.trailing, 5)
|
|
|
|
if networkManager.loadingState == .loading {
|
|
ProgressView()
|
|
.scaleEffect(0.7)
|
|
.padding(.trailing, 5)
|
|
}
|
|
}
|
|
.onChange(of: viewModel.downloadAppleSilicon) { newValue in
|
|
Task {
|
|
await networkManager.fetchProducts()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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(spacing: 10) {
|
|
Text("安装状态: ")
|
|
.font(.system(size: 14, weight: .medium))
|
|
|
|
if PrivilegedHelperManager.getHelperStatus {
|
|
HStack(spacing: 5) {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
.font(.system(size: 14))
|
|
Text("已安装 (build \(UserDefaults.standard.string(forKey: "InstalledHelperBuild") ?? "0"))")
|
|
.font(.system(size: 14))
|
|
}
|
|
.padding(.vertical, 4)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.green.opacity(0.1))
|
|
.cornerRadius(6)
|
|
} else {
|
|
HStack(spacing: 5) {
|
|
Image(systemName: "xmark.circle.fill")
|
|
.foregroundColor(.red)
|
|
.font(.system(size: 14))
|
|
Text("未安装")
|
|
.foregroundColor(.red)
|
|
.font(.system(size: 14))
|
|
}
|
|
.padding(.vertical, 4)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.red.opacity(0.1))
|
|
.cornerRadius(6)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
if isReinstallingHelper {
|
|
ProgressView()
|
|
.scaleEffect(0.8)
|
|
.padding(.trailing, 5)
|
|
}
|
|
|
|
Button(action: {
|
|
isReinstallingHelper = true
|
|
PrivilegedHelperManager.shared.removeInstallHelper()
|
|
PrivilegedHelperManager.shared.reinstallHelper { success, message in
|
|
helperAlertSuccess = success
|
|
helperAlertMessage = message
|
|
showHelperAlert = true
|
|
isReinstallingHelper = false
|
|
}
|
|
}) {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "arrow.clockwise")
|
|
.font(.system(size: 12))
|
|
Text("重新安装")
|
|
.font(.system(size: 13))
|
|
}
|
|
.frame(minWidth: 90)
|
|
}
|
|
.buttonStyle(BeautifulButtonStyle(baseColor: Color.blue.opacity(0.8)))
|
|
.foregroundColor(.white)
|
|
.disabled(isReinstallingHelper)
|
|
.help("完全卸载并重新安装 Helper")
|
|
}
|
|
|
|
if !PrivilegedHelperManager.getHelperStatus {
|
|
Text("Helper 未安装将导致无法执行需要管理员权限的操作")
|
|
.font(.caption)
|
|
.foregroundColor(.red)
|
|
}
|
|
|
|
Divider()
|
|
|
|
HStack(spacing: 10) {
|
|
Text("连接状态: ")
|
|
.font(.system(size: 14, weight: .medium))
|
|
|
|
HStack(alignment: .center, spacing: 5) {
|
|
PulsingCircle(color: helperStatusColor)
|
|
.layoutPriority(1)
|
|
|
|
Text(helperStatusText)
|
|
.font(.system(size: 14))
|
|
.foregroundColor(helperStatusColor)
|
|
}
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.padding(.vertical, 4)
|
|
.padding(.horizontal, 8)
|
|
.background(helperStatusBackgroundColor)
|
|
.cornerRadius(6)
|
|
|
|
Spacer()
|
|
|
|
Button(action: {
|
|
if PrivilegedHelperManager.getHelperStatus &&
|
|
viewModel.helperConnectionStatus != .connected {
|
|
PrivilegedHelperManager.shared.reconnectHelper { success, message in
|
|
helperAlertSuccess = success
|
|
helperAlertMessage = message
|
|
showHelperAlert = true
|
|
}
|
|
}
|
|
}) {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "network")
|
|
.font(.system(size: 12))
|
|
Text("重新连接")
|
|
.font(.system(size: 13))
|
|
}
|
|
.frame(minWidth: 90)
|
|
}
|
|
.buttonStyle(BeautifulButtonStyle(baseColor: shouldDisableReconnectButton ? Color.gray.opacity(0.6) : Color.blue.opacity(0.8)))
|
|
.foregroundColor(shouldDisableReconnectButton ? Color.white.opacity(0.8) : .white)
|
|
.disabled(shouldDisableReconnectButton)
|
|
.help("尝试重新连接到已安装的 Helper")
|
|
}
|
|
}
|
|
}
|
|
|
|
private var helperStatusColor: Color {
|
|
switch viewModel.helperConnectionStatus {
|
|
case .connected: return .green
|
|
case .connecting: return .orange
|
|
case .disconnected: return .red
|
|
case .checking: return .orange
|
|
}
|
|
}
|
|
|
|
private var shouldDisableReconnectButton: Bool {
|
|
return !PrivilegedHelperManager.getHelperStatus ||
|
|
viewModel.helperConnectionStatus == .connected ||
|
|
isReinstallingHelper
|
|
}
|
|
|
|
private var helperStatusBackgroundColor: Color {
|
|
switch viewModel.helperConnectionStatus {
|
|
case .connected: return Color.green.opacity(0.1)
|
|
case .connecting: return Color.orange.opacity(0.1)
|
|
case .disconnected: return Color.red.opacity(0.1)
|
|
case .checking: return Color.orange.opacity(0.1)
|
|
}
|
|
}
|
|
|
|
private var helperStatusText: String {
|
|
switch viewModel.helperConnectionStatus {
|
|
case .connected: return String(localized: "运行正常")
|
|
case .connecting: return String(localized: "正在连接")
|
|
case .disconnected: return String(localized: "连接断开")
|
|
case .checking: return String(localized: "检查中")
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SetupComponentRow: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
@State private var chipInfo: String = ""
|
|
|
|
private func getChipInfo() -> String {
|
|
var size = 0
|
|
sysctlbyname("machdep.cpu.brand_string", nil, &size, nil, 0)
|
|
var machine = [CChar](repeating: 0, count: size)
|
|
sysctlbyname("machdep.cpu.brand_string", &machine, &size, nil, 0)
|
|
let chipName = String(cString: machine)
|
|
|
|
if chipName.contains("Apple") {
|
|
return chipName
|
|
} else {
|
|
return chipName.components(separatedBy: "@")[0].trimmingCharacters(in: .whitespaces)
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
HStack {
|
|
Text("备份状态: ")
|
|
.font(.system(size: 14, weight: .medium))
|
|
|
|
#if DEBUG
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "ladybug.fill")
|
|
.foregroundColor(.yellow)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("Debug 模式")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.yellow.opacity(0.1))
|
|
.cornerRadius(5)
|
|
#else
|
|
if ModifySetup.isSetupBackup() {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("已备份")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.green.opacity(0.1))
|
|
.cornerRadius(5)
|
|
} else {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "xmark.circle.fill")
|
|
.foregroundColor(.red)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("未备份")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.red.opacity(0.1))
|
|
.cornerRadius(5)
|
|
|
|
Text("(可能导致处理失败)")
|
|
.foregroundColor(.red.opacity(0.8))
|
|
.font(.system(size: 12))
|
|
.padding(.leading, 2)
|
|
}
|
|
#endif
|
|
|
|
Spacer()
|
|
}
|
|
Divider()
|
|
|
|
HStack {
|
|
Text("处理状态: ")
|
|
.font(.system(size: 14, weight: .medium))
|
|
|
|
#if DEBUG
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "ladybug.fill")
|
|
.foregroundColor(.yellow)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("Debug 模式")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.yellow.opacity(0.1))
|
|
.cornerRadius(5)
|
|
#else
|
|
if ModifySetup.isSetupModified() {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("已处理")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.green.opacity(0.1))
|
|
.cornerRadius(5)
|
|
} else {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "xmark.circle.fill")
|
|
.foregroundColor(.red)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("未处理")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.red.opacity(0.1))
|
|
.cornerRadius(5)
|
|
|
|
Text("(无法使用安装功能)")
|
|
.foregroundColor(.red.opacity(0.8))
|
|
.font(.system(size: 12))
|
|
.padding(.leading, 2)
|
|
}
|
|
#endif
|
|
|
|
Spacer()
|
|
|
|
Button(action: {
|
|
if !ModifySetup.isSetupExists() {
|
|
viewModel.showDownloadAlert = true
|
|
} else {
|
|
viewModel.showReprocessConfirmAlert = true
|
|
}
|
|
}) {
|
|
Text("重新处理")
|
|
}
|
|
.buttonStyle(BeautifulButtonStyle(baseColor: Color.blue.opacity(0.8)))
|
|
.foregroundColor(.white)
|
|
}
|
|
Divider()
|
|
|
|
HStack {
|
|
Text("版本信息: ")
|
|
.font(.system(size: 14, weight: .medium))
|
|
|
|
HStack(spacing: 5) {
|
|
Image(systemName: "info.circle.fill")
|
|
.foregroundColor(.blue)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("\(viewModel.setupVersion)")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 14))
|
|
}
|
|
.padding(.vertical, 4)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.blue.opacity(0.1))
|
|
.cornerRadius(5)
|
|
|
|
Spacer()
|
|
|
|
if viewModel.isDownloadingSetup {
|
|
ProgressView(value: viewModel.setupDownloadProgress) {
|
|
Text(viewModel.setupDownloadStatus)
|
|
.font(.caption)
|
|
}
|
|
.frame(width: 150)
|
|
Button("取消") {
|
|
viewModel.cancelDownload()
|
|
}
|
|
.buttonStyle(BeautifulButtonStyle(baseColor: Color.red.opacity(0.8)))
|
|
.foregroundColor(.white)
|
|
} else {
|
|
Menu {
|
|
Button(action: {
|
|
viewModel.showDownloadConfirmAlert = true
|
|
}) {
|
|
Label("下载并处理", systemImage: "arrow.down.circle.fill")
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
|
|
Button(action: {
|
|
viewModel.showDownloadOnlyConfirmAlert = true
|
|
}) {
|
|
Label("仅下载", systemImage: "arrow.down")
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
} label: {
|
|
Text("X1a0He CC")
|
|
.frame(width: 100)
|
|
}
|
|
.menuStyle(.borderlessButton)
|
|
.fixedSize()
|
|
.help("选择下载 X1a0He CC 的方式")
|
|
}
|
|
}
|
|
}
|
|
.onAppear {
|
|
chipInfo = getChipInfo()
|
|
}
|
|
}
|
|
}
|
|
|
|
struct AutoUpdateRow: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
var body: some View {
|
|
HStack {
|
|
Toggle("", isOn: Binding(
|
|
get: { viewModel.automaticallyChecksForUpdates },
|
|
set: { viewModel.updateAutomaticallyChecksForUpdates($0) }
|
|
))
|
|
.toggleStyle(SwitchToggleStyle(tint: Color.green))
|
|
.controlSize(.small)
|
|
.labelsHidden()
|
|
|
|
Text("自动检查更新版本")
|
|
.font(.system(size: 14))
|
|
|
|
Spacer()
|
|
|
|
if viewModel.automaticallyChecksForUpdates {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("已启用")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.green.opacity(0.1))
|
|
.cornerRadius(5)
|
|
}
|
|
|
|
CheckForUpdatesView(updater: viewModel.updater)
|
|
.buttonStyle(BeautifulButtonStyle(baseColor: Color.blue.opacity(0.8)))
|
|
.foregroundColor(.white)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct AutoDownloadRow: View {
|
|
@ObservedObject var viewModel: GeneralSettingsViewModel
|
|
|
|
var body: some View {
|
|
HStack {
|
|
Toggle("", isOn: Binding(
|
|
get: { viewModel.automaticallyDownloadsUpdates },
|
|
set: { viewModel.updateAutomaticallyDownloadsUpdates($0) }
|
|
))
|
|
.toggleStyle(SwitchToggleStyle(tint: Color.green))
|
|
.controlSize(.small)
|
|
.labelsHidden()
|
|
.disabled(viewModel.isAutomaticallyDownloadsUpdatesDisabled)
|
|
|
|
Text("自动下载最新版本")
|
|
.font(.system(size: 14))
|
|
.foregroundColor(viewModel.isAutomaticallyDownloadsUpdatesDisabled ? .gray : .primary)
|
|
|
|
Spacer()
|
|
|
|
if viewModel.automaticallyDownloadsUpdates && !viewModel.isAutomaticallyDownloadsUpdatesDisabled {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("已启用")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.green.opacity(0.1))
|
|
.cornerRadius(5)
|
|
} else if viewModel.isAutomaticallyDownloadsUpdatesDisabled {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
.foregroundColor(.gray)
|
|
.font(.system(size: 12))
|
|
.frame(width: 16, height: 16)
|
|
Text("需先启用自动检查")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 13))
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 8)
|
|
.background(Color.gray.opacity(0.1))
|
|
.cornerRadius(5)
|
|
}
|
|
}
|
|
}
|
|
}
|