From 83abcc7316bda247375a0e97f8fa66e8ae0b6f65 Mon Sep 17 00:00:00 2001 From: X1a0He Date: Thu, 6 Mar 2025 01:40:01 +0800 Subject: [PATCH] refactor: AboutView, AppCardView, VersionPickerView 1. AboutView: Subdivided some Views 2. AboutView: Added the display of system version number and specific chip model 3. AppCardView: Added the display of the minimum system version and module display 4. AppCardView: Optimized the display of the number of available versions and the number of dependent components 5. VersionPickerView: Added the product icon display in the Header part 6. VersionPickerView: Optimized the download architecture prompt 7. VersionPickerView: Optimized the display of product version, provided the display of productVersion and buildGuid 8. VersionPickerView: Optimized the display of dependent components, added the display of total number of dependencies and buildGuid 9. VersionPickerView: In DEBUG mode, you can check whether the dependent component hits the correct version, the selected version, and the reason for not hitting, and added the selected version icon 10. VersionPickerView: Adjusted the version display of dependent components, and no longer displayed baseVersion by default 11. VersionPickerView: Added optional module display and information --- .gitignore | 2 - .../xcschemes/Adobe Downloader.xcscheme | 2 +- Adobe Downloader/Commons/Globals.swift | 19 + Adobe Downloader/Commons/NewStructs.swift | 15 + Adobe Downloader/ContentView.swift | 21 +- Adobe Downloader/Utils/InstallManager.swift | 12 +- Adobe Downloader/Utils/NewJSONParser.swift | 54 +- Adobe Downloader/Views/AboutView.swift | 487 +++++++++++------- Adobe Downloader/Views/AppCardView.swift | 57 +- .../Views/VersionPickerView.swift | 378 ++++++++++---- Localizables/Localizable.xcstrings | 91 +++- 11 files changed, 803 insertions(+), 335 deletions(-) diff --git a/.gitignore b/.gitignore index 24cf675..28111c5 100644 --- a/.gitignore +++ b/.gitignore @@ -26,13 +26,11 @@ DerivedData/ !default.mode2v3 *.perspectivev3 !default.perspectivev3 -xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint -*.xcuserstate .xcuserstate ## Obj-C/Swift specific diff --git a/Adobe Downloader.xcodeproj/xcshareddata/xcschemes/Adobe Downloader.xcscheme b/Adobe Downloader.xcodeproj/xcshareddata/xcschemes/Adobe Downloader.xcscheme index 6ed2fb3..a71041f 100644 --- a/Adobe Downloader.xcodeproj/xcshareddata/xcschemes/Adobe Downloader.xcscheme +++ b/Adobe Downloader.xcodeproj/xcshareddata/xcschemes/Adobe Downloader.xcscheme @@ -31,7 +31,7 @@ shouldAutocreateTestPlan = "YES"> 0 + }).first { + if let matchingPlatform = latestProduct.platforms.first(where: { platform in + platform.id == targetPlatform || platform.id == "macuniversal" + }), let firstLanguageSet = matchingPlatform.languageSet.first { productVersion = firstLanguageSet.productVersion buildGuid = firstLanguageSet.buildGuid + isMatchPlatform = true + selectedPlatform = matchingPlatform.id + selectedReason = matchingPlatform.id == "macuniversal" ? + "成功匹配通用平台 macuniversal(支持所有 Mac 平台)" : + "成功匹配目标平台" + } else { + if let firstAvailablePlatform = latestProduct.platforms.first, + let firstLanguageSet = firstAvailablePlatform.languageSet.first { + productVersion = firstLanguageSet.productVersion + buildGuid = firstLanguageSet.buildGuid + isMatchPlatform = false + selectedPlatform = firstAvailablePlatform.id + selectedReason = "当前依赖所有版本中无匹配平台,使用可用平台: \(firstAvailablePlatform.id)" + } else { + selectedReason = "未找到任何可用平台" + } } + } else { + selectedReason = "未找到最新版本产品" } + } else { + selectedReason = "globalStiResult.products 为空" } - return Product.Platform.LanguageSet.Dependency( + let dependency = Product.Platform.LanguageSet.Dependency( sapCode: sapCode, baseVersion: baseVersion, productVersion: productVersion, - buildGuid: buildGuid + buildGuid: buildGuid, + isMatchPlatform: isMatchPlatform, + targetPlatform: targetPlatform, + selectedPlatform: selectedPlatform, + selectedReason: selectedReason ) + + globalDependencyCache[cacheKey] = dependency + + return dependency } newLanguageSet.dependencies.append(contentsOf: dependencies) } diff --git a/Adobe Downloader/Views/AboutView.swift b/Adobe Downloader/Views/AboutView.swift index 92e977c..368cab4 100644 --- a/Adobe Downloader/Views/AboutView.swift +++ b/Adobe Downloader/Views/AboutView.swift @@ -15,10 +15,10 @@ private enum AboutViewConstants { 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"), @@ -30,7 +30,7 @@ private enum AboutViewConstants { struct ExternalLinkView: View { let title: String let url: String - + var body: some View { Link(title, destination: URL(string: url)!) .font(.system(size: AboutViewConstants.linkFontSize)) @@ -41,11 +41,11 @@ struct ExternalLinkView: View { 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) @@ -53,19 +53,19 @@ struct AboutView: View { 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") @@ -84,7 +84,7 @@ struct AboutAppView: View { private var appVersion: String { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" } - + var body: some View { VStack(spacing: AboutViewConstants.verticalSpacing) { appIconSection @@ -95,31 +95,31 @@ struct AboutAppView: View { .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)) @@ -130,7 +130,7 @@ struct AboutAppView: View { struct PulsingCircle: View { let color: Color @State private var scale: CGFloat = 1.0 - + var body: some View { Circle() .fill(color) @@ -171,25 +171,25 @@ final class GeneralSettingsViewModel: ObservableObject { 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 { + set { StorageData.shared.confirmRedownload = newValue objectWillChange.send() } @@ -215,9 +215,9 @@ final class GeneralSettingsViewModel: ObservableObject { 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 @@ -231,9 +231,9 @@ final class GeneralSettingsViewModel: ObservableObject { } } .store(in: &cancellables) - + PrivilegedHelperManager.shared.executeCommand("whoami") { _ in } - + NotificationCenter.default.publisher(for: .storageDidChange) .receive(on: RunLoop.main) .sink { [weak self] _ in @@ -277,60 +277,40 @@ struct GeneralSettingsView: View { } var body: some View { - ScrollView { - VStack(spacing: 16) { - DownloadSettingsView(viewModel: viewModel) - HelperSettingsView(viewModel: viewModel, - showHelperAlert: $showHelperAlert, - helperAlertMessage: $helperAlertMessage, - helperAlertSuccess: $helperAlertSuccess) - CCSettingsView(viewModel: viewModel) - UpdateSettingsView(viewModel: viewModel) - CleanConfigView() - } - .padding() + 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) + HelperSettingsView(viewModel: viewModel, + showHelperAlert: $showHelperAlert, + helperAlertMessage: $helperAlertMessage, + helperAlertSuccess: $helperAlertSuccess) + CCSettingsView(viewModel: viewModel) + UpdateSettingsView(viewModel: viewModel) + CleanConfigView() } + .padding() .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .alert(helperAlertSuccess ? "操作成功" : "操作失败", isPresented: $showHelperAlert) { - Button("确定") { } - } message: { - Text(helperAlertMessage) - } - .alert("需要下载 Setup 组件", isPresented: $viewModel.showDownloadAlert) { - Button("取消", role: .cancel) { } - Button("下载") { - Task { - await downloadSetup(shouldProcess: false) - } - } - } message: { - Text("检测到系统中不存在 Setup 组件,需要先下载组件才能继续操作。") - } - .alert("确认下载并处理", isPresented: $viewModel.showDownloadConfirmAlert) { - Button("取消", role: .cancel) { } - Button("确定") { - Task { - await downloadSetup(shouldProcess: true) - } - } - } message: { - Text("确定要下载并处理 X1a0He CC 吗?这将完成下载并自动对 Setup 组件进行处理") - } - .alert("确认下载", isPresented: $viewModel.showReprocessConfirmAlert) { - Button("取消", role: .cancel) { } - Button("确定") { - Task { - await downloadSetup(shouldProcess: false) - } - } - } message: { - Text("确定要下载 X1a0He CC 吗?下载完成后需要手动处理。") - } - .alert(viewModel.isSuccess ? "操作成功" : "操作失败", isPresented: $viewModel.showAlert) { - Button("确定") { } - } message: { - Text(viewModel.alertMessage) - } + .modifier(GeneralSettingsAlerts( + viewModel: viewModel, + showHelperAlert: $showHelperAlert, + helperAlertMessage: $helperAlertMessage, + helperAlertSuccess: $helperAlertSuccess + )) .task { viewModel.setupVersion = ModifySetup.checkComponentVersion() } @@ -338,33 +318,89 @@ struct GeneralSettingsView: View { 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 - private func downloadSetup(shouldProcess: Bool) async { + 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 { + startDownloadSetup(shouldProcess: false) + } + } + } message: { + Text("确定要下载 X1a0He CC 吗?下载完成后需要手动处理。") + } + .alert(viewModel.isSuccess ? "操作成功" : "操作失败", isPresented: $viewModel.showAlert) { + Button("确定") { } + } message: { + Text(viewModel.alertMessage) + } + } + + private func startDownloadSetup(shouldProcess: Bool) { viewModel.isDownloadingSetup = true viewModel.isCancelled = false - 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 = shouldProcess ? - String(localized: "X1a0He CC 下载并处理成功") : - String(localized: "X1a0He CC 下载成功") - } catch NetworkError.cancelled { - viewModel.isSuccess = false - viewModel.alertMessage = String(localized: "下载已取消") - } catch { - viewModel.isSuccess = false - viewModel.alertMessage = error.localizedDescription + + 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 } - viewModel.showAlert = true - viewModel.isDownloadingSetup = false } } @@ -438,9 +474,9 @@ struct UpdateSettingsView: View { .foregroundColor(.secondary) } .font(.system(size: 12)) - + Divider() - + AutoUpdateRow(viewModel: viewModel) Divider() AutoDownloadRow(viewModel: viewModel) @@ -454,19 +490,50 @@ struct CleanConfigView: View { @State private var showConfirmation = false @State private var showAlert = false @State private var alertMessage = "" + @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 { - GroupBox(label: Text("重置程序").padding(.bottom, 8)) { - VStack(alignment: .leading, spacing: 12) { - HStack { - Button("重置程序") { - showConfirmation = true + HStack(spacing: 12) { + GroupBox(label: Text("重置程序").padding(.bottom, 8)) { + VStack(alignment: .leading, spacing: 12) { + HStack { + Button("重置程序") { + showConfirmation = true + } + .buttonStyle(.borderedProminent) + .tint(.red) } - .buttonStyle(.borderedProminent) - .tint(.red) } + .padding(8) + } + + GroupBox(label: Text("系统信息").padding(.bottom, 8)) { + VStack(alignment: .leading, spacing: 12) { + HStack { + Image(systemName: "desktopcomputer") + .foregroundColor(.blue) + Text("macOS \(ProcessInfo.processInfo.operatingSystemVersionString)") + Text("[\(chipInfo.isEmpty ? "加载中..." : chipInfo)]") + .foregroundColor(.secondary) + Spacer() + } + } + .padding(8) } - .padding(8) } .alert("确认重置程序", isPresented: $showConfirmation) { Button("取消", role: .cancel) { } @@ -481,26 +548,29 @@ struct CleanConfigView: View { } message: { Text(alertMessage) } + .onAppear { + chipInfo = getChipInfo() + } } - + private func cleanConfig() { do { - let downloadsURL = try FileManager.default.url(for: .downloadsDirectory, - in: .userDomainMask, - appropriateFor: nil, + let downloadsURL = try FileManager.default.url(for: .downloadsDirectory, + in: .userDomainMask, + appropriateFor: nil, create: false) let scriptURL = downloadsURL.appendingPathComponent("clean-config.sh") - + guard let scriptPath = Bundle.main.path(forResource: "clean-config", ofType: "sh"), let scriptContent = try? String(contentsOfFile: scriptPath, encoding: .utf8) else { throw NSError(domain: "ScriptError", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法读取脚本文件"]) } - + try scriptContent.write(to: scriptURL, atomically: true, encoding: .utf8) - - try FileManager.default.setAttributes([.posixPermissions: 0o755], + + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: scriptURL.path) - + if PrivilegedHelperManager.getHelperStatus { PrivilegedHelperManager.shared.executeCommand("open -a Terminal \(scriptURL.path)") { output in if output.isEmpty { @@ -514,7 +584,7 @@ struct CleanConfigView: View { } } else { let terminalURL = URL(fileURLWithPath: "/System/Applications/Utilities/Terminal.app") - NSWorkspace.shared.open([scriptURL], + NSWorkspace.shared.open([scriptURL], withApplicationAt: terminalURL, configuration: NSWorkspace.OpenConfiguration()) { _, error in if let error = error { @@ -522,13 +592,13 @@ struct CleanConfigView: View { showAlert = true return } - + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { exit(0) } } } - + } catch { alertMessage = "清空配置失败: \(error.localizedDescription)" showAlert = true @@ -541,7 +611,7 @@ private class PreviewUpdater: SPUUpdater { let hostBundle = Bundle.main let applicationBundle = Bundle.main let userDriver = SPUStandardUserDriver(hostBundle: hostBundle, delegate: nil) - + super.init( hostBundle: hostBundle, applicationBundle: applicationBundle, @@ -549,12 +619,12 @@ private class PreviewUpdater: SPUUpdater { delegate: nil ) } - + override var automaticallyChecksForUpdates: Bool { get { true } set { } } - + override var automaticallyDownloadsUpdates: Bool { get { true } set { } @@ -644,13 +714,12 @@ struct RedownloadConfirmRow: View { 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) - .disabled(networkManager.loadingState == .loading) + .disabled(globalNetworkManager.loadingState == .loading) Spacer() Text("当前架构: \(AppStatics.cpuArchitecture)") .foregroundColor(.secondary) @@ -659,7 +728,7 @@ struct ArchitectureSettingRow: View { } .onChange(of: viewModel.downloadAppleSilicon) { newValue in Task { - await networkManager.fetchProducts() + await globalNetworkManager.fetchProducts() } } } @@ -671,7 +740,7 @@ struct HelperStatusRow: View { @Binding var helperAlertMessage: String @Binding var helperAlertSuccess: Bool @State private var isReinstallingHelper = false - + var body: some View { VStack(alignment: .leading, spacing: 8) { HStack { @@ -687,13 +756,13 @@ struct HelperStatusRow: View { .foregroundColor(.red) } Spacer() - + if isReinstallingHelper { ProgressView() .scaleEffect(0.7) .frame(width: 16, height: 16) } - + Button(action: { isReinstallingHelper = true PrivilegedHelperManager.shared.removeInstallHelper() @@ -709,13 +778,13 @@ struct HelperStatusRow: View { .disabled(isReinstallingHelper) .help("完全卸载并重新安装 Helper") } - + if !PrivilegedHelperManager.getHelperStatus { Text("Helper 未安装将导致无法执行需要管理员权限的操作") .font(.caption) .foregroundColor(.red) } - + Divider() HStack { @@ -724,11 +793,11 @@ struct HelperStatusRow: View { .padding(.horizontal, 4) Text(helperStatusText) .foregroundColor(helperStatusColor) - + Spacer() - + Button(action: { - if PrivilegedHelperManager.getHelperStatus && + if PrivilegedHelperManager.getHelperStatus && viewModel.helperConnectionStatus != .connected { PrivilegedHelperManager.shared.reconnectHelper { success, message in helperAlertSuccess = success @@ -739,14 +808,14 @@ struct HelperStatusRow: View { }) { Text("重新连接") } - .disabled(!PrivilegedHelperManager.getHelperStatus || + .disabled(!PrivilegedHelperManager.getHelperStatus || viewModel.helperConnectionStatus == .connected || isReinstallingHelper) .help("尝试重新连接到已安装的 Helper") } } } - + private var helperStatusColor: Color { switch viewModel.helperConnectionStatus { case .connected: return .green @@ -755,7 +824,7 @@ struct HelperStatusRow: View { case .checking: return .orange } } - + private var helperStatusText: String { switch viewModel.helperConnectionStatus { case .connected: return String(localized: "运行正常") @@ -768,6 +837,21 @@ struct HelperStatusRow: View { 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) { @@ -825,7 +909,6 @@ struct SetupComponentRow: View { Image(systemName: "info.circle.fill") .foregroundColor(.blue) Text("\(viewModel.setupVersion)") - Text(" [\(AppStatics.cpuArchitecture)]") Spacer() if viewModel.isDownloadingSetup { @@ -845,7 +928,7 @@ struct SetupComponentRow: View { Label("下载并处理", systemImage: "arrow.down.circle.fill") .frame(maxWidth: .infinity, alignment: .leading) } - + Button(action: { viewModel.showReprocessConfirmAlert = true }) { @@ -862,6 +945,9 @@ struct SetupComponentRow: View { } } } + .onAppear { + chipInfo = getChipInfo() + } } } @@ -901,22 +987,22 @@ struct QAView: View { question: String(localized: "为什么需要安装 Helper?"), answer: String(localized: "Helper 是一个具有管理员权限的辅助工具,用于执行需要管理员权限的操作,如修改系统文件等。没有 Helper 将无法正常使用软件的某些功能。") ) - + QAItem( question: String(localized: "为什么需要下载 Setup 组件?"), answer: String(localized: "Setup 组件是 Adobe 官方的安装程序组件,我们需要对其进行修改以实现绕过验证的功能。如果没有下载并处理 Setup 组件,将无法使用安装功能。") ) - + QAItem( question: String(localized: "为什么有时候下载会失败?"), answer: String(localized: "下载失败可能有多种原因:\n1. 网络连接不稳定\n2. Adobe 服务器响应超时\n3. 本地磁盘空间不足\n建议您检查网络连接并重试,如果问题持续存在,可以尝试使用代理或 VPN。") ) - + QAItem( question: String(localized: "如何修复安装失败的问题?"), answer: String(localized: "如果安装失败,您可以尝试以下步骤:\n1. 确保已正确安装并连接 Helper\n2. 确保已下载并处理 Setup 组件\n3. 检查磁盘剩余空间是否充足\n4. 尝试重新下载并安装\n如果问题仍然存在,可以尝试重新安装 Helper 和重新处理 Setup 组件。") ) - + QAItem( question: String(localized: "为什么我安装的时候会遇到错误代码,错误代码表示什么意思?"), answer: String(localized: "• 错误 2700:不太可能会出现,除非 Setup 组件处理失败了\n• 错误 107:所下载的文件架构与系统架构不一致或者安装文件被损坏\n• 错误 103:出现权限问题,请确保 Helper 状态正常\n• 错误 182:文件不齐全或文件被损坏,或者你的Setup组件不一致,请重新下载 X1a0He CC\n• 错误 133:系统磁盘空间不足\n• 错误 -1:Setup 组件未处理或处理失败,请联系开发者\n• 错误 195:所下载的产品不支持你当前的系统\n• 错误 146:请在 Mac 系统设置中给予 Adobe Downloader 全磁盘权限\n• 错误 255:Setup 组件需要更新,请联系开发者解决") @@ -932,18 +1018,18 @@ struct QAView: View { struct QAItem: View { let question: String let answer: String - + var body: some View { VStack(alignment: .leading, spacing: 8) { Text(question) .font(.headline) .foregroundColor(.primary) - + Text(answer) .font(.body) .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) - + Divider() } } @@ -955,14 +1041,14 @@ struct CleanupLog: Identifiable { let command: String let status: LogStatus let message: String - + enum LogStatus { case running case success case error case cancelled } - + static func getCleanupDescription(for command: String) -> String { if command.contains("Library/Logs") || command.contains("DiagnosticReports") { if command.contains("Adobe Creative Cloud") { @@ -1012,7 +1098,7 @@ struct CleanupView: View { @State private var expandedOptions = Set() @State private var isCancelled = false @State private var isLogExpanded = false - + enum CleanupOption: String, CaseIterable, Identifiable { case adobeApps = "Adobe 应用程序" case adobeCreativeCloud = "Adobe Creative Cloud" @@ -1024,9 +1110,9 @@ struct CleanupView: View { case adobeKeychain = "Adobe 钥匙串" case adobeGenuineService = "Adobe 正版验证服务" case adobeHosts = "Adobe Hosts" - + var id: String { self.rawValue } - + var localizedName: String { switch self { case .adobeApps: @@ -1051,7 +1137,7 @@ struct CleanupView: View { return String(localized: "Adobe Hosts") } } - + var commands: [String] { switch self { case .adobeApps: @@ -1218,7 +1304,7 @@ struct CleanupView: View { ] } } - + var description: String { switch self { case .adobeApps: @@ -1244,7 +1330,7 @@ struct CleanupView: View { } } } - + var body: some View { VStack(alignment: .leading) { Text("选择要清理的内容") @@ -1254,7 +1340,7 @@ struct CleanupView: View { Text("注意:清理过程不会影响 Adobe Downloader 的文件和下载数据") .font(.subheadline) .foregroundColor(.secondary) - + ScrollView(showsIndicators: false) { VStack(alignment: .leading) { ForEach(CleanupOption.allCases) { option in @@ -1284,7 +1370,7 @@ struct CleanupView: View { } .disabled(isProcessing) .labelsHidden() - + VStack(alignment: .leading, spacing: 4) { Text(option.localizedName) .font(.system(size: 14, weight: .medium)) @@ -1292,9 +1378,9 @@ struct CleanupView: View { .font(.system(size: 12)) .foregroundColor(.secondary) } - + Spacer() - + Image(systemName: expandedOptions.contains(option) ? "chevron.down" : "chevron.right") .foregroundColor(.secondary) .rotationEffect(.degrees(expandedOptions.contains(option) ? 0 : -90)) @@ -1305,7 +1391,7 @@ struct CleanupView: View { } .buttonStyle(.plain) .disabled(isProcessing) - + if expandedOptions.contains(option) { VStack(alignment: .leading, spacing: 4) { Text("将执行的命令:") @@ -1313,7 +1399,7 @@ struct CleanupView: View { .foregroundColor(.secondary) .padding(.top, 4) .padding(.horizontal, 6) - + ForEach(option.commands, id: \.self) { command in Text(command) .font(.system(size: 11, design: .monospaced)) @@ -1343,7 +1429,7 @@ struct CleanupView: View { } .disabled(isProcessing) .labelsHidden() - + VStack(alignment: .leading, spacing: 4) { Text(option.localizedName) .font(.system(size: 14, weight: .medium)) @@ -1351,7 +1437,7 @@ struct CleanupView: View { .font(.system(size: 12)) .foregroundColor(.secondary) } - + Spacer() } .padding(.vertical, 8) @@ -1363,10 +1449,10 @@ struct CleanupView: View { } } } - + Divider() .padding(.vertical, 8) - + VStack(alignment: .leading, spacing: 8) { if isProcessing { HStack { @@ -1375,7 +1461,7 @@ struct CleanupView: View { .font(.system(size: 12)) } .progressViewStyle(LinearProgressViewStyle()) - + Button(action: { isCancelled = true }) { @@ -1383,7 +1469,7 @@ struct CleanupView: View { } .disabled(isCancelled) } - + if let lastLog = cleanupLogs.last { #if DEBUG Text("当前执行:\(lastLog.command)") @@ -1400,7 +1486,7 @@ struct CleanupView: View { #endif } } - + VStack(alignment: .leading, spacing: 4) { Button(action: { withAnimation { @@ -1410,15 +1496,15 @@ struct CleanupView: View { HStack { Text("最近日志:") .font(.system(size: 12, weight: .medium)) - + if isProcessing { Text("正在执行...") .font(.system(size: 12)) .foregroundColor(.secondary) } - + Spacer() - + Image(systemName: isLogExpanded ? "chevron.down" : "chevron.right") .foregroundColor(.secondary) } @@ -1426,7 +1512,7 @@ struct CleanupView: View { .contentShape(Rectangle()) } .buttonStyle(.plain) - + ScrollView { VStack(alignment: .leading, spacing: 4) { if cleanupLogs.isEmpty { @@ -1457,7 +1543,7 @@ struct CleanupView: View { .background(Color(NSColor.textBackgroundColor)) .cornerRadius(6) } - + HStack { Button(action: { selectedOptions = Set(CleanupOption.allCases) @@ -1465,14 +1551,14 @@ struct CleanupView: View { Text("全选") } .disabled(isProcessing) - + Button(action: { selectedOptions.removeAll() }) { Text("取消全选") } .disabled(isProcessing) - + #if DEBUG Button(action: { if expandedOptions.count == CleanupOption.allCases.count { @@ -1485,9 +1571,9 @@ struct CleanupView: View { } .disabled(isProcessing) #endif - + Spacer() - + Button(action: { if !selectedOptions.isEmpty { showConfirmation = true @@ -1516,15 +1602,15 @@ struct CleanupView: View { ) } } - + private func cleanupSelectedItems() { isProcessing = true cleanupLogs.removeAll() currentCommandIndex = 0 isCancelled = false - + let userHome = NSHomeDirectory() - + var commands: [String] = [] for option in selectedOptions { let userCommands = option.commands.map { command in @@ -1534,10 +1620,10 @@ struct CleanupView: View { } totalCommands = commands.count - + executeNextCommand(commands: commands) } - + private func executeNextCommand(commands: [String]) { guard currentCommandIndex < commands.count else { DispatchQueue.main.async { @@ -1548,7 +1634,7 @@ struct CleanupView: View { } return } - + if isCancelled { DispatchQueue.main.async { isProcessing = false @@ -1558,7 +1644,7 @@ struct CleanupView: View { } return } - + let command = commands[currentCommandIndex] cleanupLogs.append(CleanupLog( timestamp: Date(), @@ -1566,7 +1652,7 @@ struct CleanupView: View { status: .running, message: String(localized: "正在执行...") )) - + let timeoutTimer = DispatchSource.makeTimerSource(queue: .global()) timeoutTimer.schedule(deadline: .now() + 30) timeoutTimer.setEventHandler { [self] in @@ -1584,7 +1670,7 @@ struct CleanupView: View { } } timeoutTimer.resume() - + PrivilegedHelperManager.shared.executeCommand(command) { [self] output in timeoutTimer.cancel() DispatchQueue.main.async { @@ -1616,7 +1702,7 @@ struct CleanupView: View { } } } - + private func statusIcon(for status: CleanupLog.LogStatus) -> String { switch status { case .running: @@ -1629,7 +1715,7 @@ struct CleanupView: View { return "xmark.circle.fill" } } - + private func statusColor(for status: CleanupLog.LogStatus) -> Color { switch status { case .running: @@ -1642,7 +1728,7 @@ struct CleanupView: View { return .orange } } - + private func timeString(from date: Date) -> String { let formatter = DateFormatter() formatter.dateFormat = "HH:mm:ss" @@ -1653,16 +1739,16 @@ struct CleanupView: View { struct LogEntryView: View { let log: CleanupLog @State private var showCopyButton = false - + var body: some View { HStack { Image(systemName: statusIcon(for: log.status)) .foregroundColor(statusColor(for: log.status)) - + Text(timeString(from: log.timestamp)) .font(.system(size: 11)) .foregroundColor(.secondary) - + #if DEBUG Text(log.command) .font(.system(size: 11, design: .monospaced)) @@ -1674,15 +1760,15 @@ struct LogEntryView: View { .lineLimit(1) .truncationMode(.middle) #endif - + Spacer() - + if log.status == .error && !log.message.isEmpty { HStack(spacing: 4) { Text(truncatedErrorMessage(log.message)) .font(.system(size: 11)) .foregroundColor(.secondary) - + Button(action: { copyToClipboard(log.message) }) { @@ -1701,7 +1787,7 @@ struct LogEntryView: View { .padding(.vertical, 4) .padding(.horizontal, 8) } - + private func truncatedErrorMessage(_ message: String) -> String { if message.hasPrefix("执行失败:") { let errorMessage = String(message.dropFirst(5)) @@ -1711,12 +1797,12 @@ struct LogEntryView: View { } return message } - + private func copyToClipboard(_ message: String) { NSPasteboard.general.clearContents() NSPasteboard.general.setString(message, forType: .string) } - + private func statusIcon(for status: CleanupLog.LogStatus) -> String { switch status { case .running: @@ -1729,7 +1815,7 @@ struct LogEntryView: View { return "xmark.circle.fill" } } - + private func statusColor(for status: CleanupLog.LogStatus) -> Color { switch status { case .running: @@ -1742,7 +1828,7 @@ struct LogEntryView: View { return .orange } } - + private func timeString(from date: Date) -> String { let formatter = DateFormatter() formatter.dateFormat = "HH:mm:ss" @@ -1777,3 +1863,4 @@ struct LogEntryView: View { AboutView(updater: PreviewUpdater()) .environmentObject(NetworkManager()) } + diff --git a/Adobe Downloader/Views/AppCardView.swift b/Adobe Downloader/Views/AppCardView.swift index 73892d4..05c6542 100644 --- a/Adobe Downloader/Views/AppCardView.swift +++ b/Adobe Downloader/Views/AppCardView.swift @@ -369,13 +369,54 @@ private struct ProductInfoView: View { .lineLimit(2) .multilineTextAlignment(.center) - HStack(spacing: 4) { - let product = findProduct(id: viewModel.uniqueProduct.id) - let versions = Set(product?.platforms.first?.languageSet.map { $0.productVersion } ?? []) - let dependenciesCount = product?.platforms.first?.languageSet.first?.dependencies.count ?? 0 - Text("可用版本: \(versions.count)") - Text("|") - Text("依赖包: \(dependenciesCount)") + let products = findProducts(id: viewModel.uniqueProduct.id) + let versions = products.compactMap { product -> String? in + let platforms = product.platforms.filter { platform in + StorageData.shared.allowedPlatform.contains(platform.id) + } + return platforms.isEmpty ? nil : product.version + } + let uniqueVersions = Set(versions) + + let dependenciesCount = products.first?.platforms.first?.languageSet.first?.dependencies.count ?? 0 + let minOSVersion = products.first?.platforms.first?.range.first?.min ?? "" + let modulesCount = products.first?.platforms.first?.modules.count ?? 0 + + HStack(spacing: 12) { + HStack(spacing: 2) { + Image(systemName: "tag") + Text("\(uniqueVersions.count)") + } + + if dependenciesCount > 0 { + Text("•") + .foregroundColor(.gray) + + HStack(spacing: 2) { + Image(systemName: "shippingbox") + Text("\(dependenciesCount)") + } + } + + if !minOSVersion.isEmpty { + Text("•") + .foregroundColor(.gray) + + HStack(spacing: 2) { + Image(systemName: "macwindow") + Text(minOSVersion.replacingOccurrences(of: "-", with: "")) + } + } + + if modulesCount > 0 { + Text("•") + .foregroundColor(.gray) + + HStack(spacing: 2) { + Image(systemName: "square.stack.3d.up") + Text("\(modulesCount)") + } + } } .font(.caption) .foregroundColor(.secondary) @@ -431,7 +472,7 @@ private struct SheetModifier: ViewModifier { content .sheet(isPresented: $viewModel.showVersionPicker) { if let product = findProduct(id: viewModel.uniqueProduct.id) { - VersionPickerView(product: product) { version in + VersionPickerView(productId: viewModel.uniqueProduct.id) { version in Task { await viewModel.handleDownloadRequest( version, diff --git a/Adobe Downloader/Views/VersionPickerView.swift b/Adobe Downloader/Views/VersionPickerView.swift index da927e8..f02b284 100644 --- a/Adobe Downloader/Views/VersionPickerView.swift +++ b/Adobe Downloader/Views/VersionPickerView.swift @@ -8,8 +8,8 @@ import SwiftUI private enum VersionPickerConstants { static let headerPadding: CGFloat = 5 - static let viewWidth: CGFloat = 400 - static let viewHeight: CGFloat = 500 + static let viewWidth: CGFloat = 500 + static let viewHeight: CGFloat = 600 static let iconSize: CGFloat = 32 static let verticalSpacing: CGFloat = 8 static let horizontalSpacing: CGFloat = 12 @@ -26,19 +26,19 @@ struct VersionPickerView: View { @StorageValue(\.downloadAppleSilicon) private var downloadAppleSilicon @State private var expandedVersions: Set = [] - private let product: Product + private let productId: String private let onSelect: (String) -> Void - init(product: Product, onSelect: @escaping (String) -> Void) { - self.product = product + init(productId: String, onSelect: @escaping (String) -> Void) { + self.productId = productId self.onSelect = onSelect } var body: some View { VStack(spacing: 0) { - VersionPickerHeaderView(product: product, downloadAppleSilicon: downloadAppleSilicon) + VersionPickerHeaderView(productId: productId, downloadAppleSilicon: downloadAppleSilicon) VersionListView( - product: product, + productId: productId, expandedVersions: $expandedVersions, onSelect: onSelect, dismiss: dismiss @@ -49,16 +49,32 @@ struct VersionPickerView: View { } private struct VersionPickerHeaderView: View { - let product: Product + let productId: String let downloadAppleSilicon: Bool @Environment(\.dismiss) private var dismiss - @EnvironmentObject private var networkManager: NetworkManager var body: some View { VStack { HStack { - Text("\(product.displayName)") - .font(.headline) + if let product = findProduct(id: productId) { + if let icon = product.getBestIcon() { + AsyncImage(url: URL(string: icon.value)) { image in + image + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 24, height: 24) + } placeholder: { + Image(systemName: "app.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 24, height: 24) + .foregroundColor(.blue) + } + } + + Text("\(product.displayName)") + .font(.headline) + } Text("选择版本") .foregroundColor(.secondary) Spacer() @@ -68,10 +84,20 @@ private struct VersionPickerHeaderView: View { } .padding(.bottom, VersionPickerConstants.headerPadding) - Text("🔔 即将下载 \(downloadAppleSilicon ? "Apple Silicon" : "Intel") (\(platformText)) 版本 🔔") - .font(.caption) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.bottom, 10) + HStack(spacing: 6) { + Image(systemName: downloadAppleSilicon ? "m.square" : "x.square") + .foregroundColor(.blue) + Text(downloadAppleSilicon ? "Apple Silicon" : "Intel") + .font(.caption) + .fontWeight(.medium) + Text("•") + .foregroundColor(.secondary) + Text(platformText) + .font(.caption) + .foregroundColor(.secondary) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.bottom, 10) } .padding(.horizontal) .padding(.top) @@ -84,51 +110,64 @@ private struct VersionPickerHeaderView: View { } private struct VersionListView: View { - @EnvironmentObject private var networkManager: NetworkManager - let product: Product + let productId: String @Binding var expandedVersions: Set let onSelect: (String) -> Void let dismiss: DismissAction var body: some View { ScrollView(showsIndicators: false) { - LazyVStack(spacing: VersionPickerConstants.verticalSpacing) { - ForEach(filteredVersions, id: \.key) { version, info in - VersionRow( - product: product, - version: version, - info: info, - isExpanded: expandedVersions.contains(version), - onSelect: handleVersionSelect, - onToggle: handleVersionToggle - ) + VStack(spacing: 0) { + LazyVStack(spacing: VersionPickerConstants.verticalSpacing) { + ForEach(filteredVersions, id: \.key) { version, info in + VersionRow( + productId: productId, + version: version, + info: info, + isExpanded: expandedVersions.contains(version), + onSelect: handleVersionSelect, + onToggle: handleVersionToggle + ) + } } + .padding() + + HStack(spacing: 8) { + Capsule() + .fill(Color.secondary.opacity(0.2)) + .frame(width: 6, height: 6) + Text("获取到 \(filteredVersions.count) 个版本") + .font(.system(size: 12)) + .foregroundColor(.secondary) + } + .padding(.bottom, 16) } - .padding() } .background(Color(NSColor.windowBackgroundColor)) } private var filteredVersions: [(key: String, value: Product.Platform)] { - // 获取支持的平台 - let platforms = product.platforms.filter { platform in - StorageData.shared.allowedPlatform.contains(platform.id) && - platform.languageSet.first != nil - } - - // 如果没有支持的平台,返回空数组 - if platforms.isEmpty { + let products = findProducts(id: productId) + if products.isEmpty { return [] } + + var versionPlatformMap: [String: Product.Platform] = [:] - // 将平台按版本号降序排序 - return platforms.map { platform in - // 使用第一个语言集的 productVersion 作为版本号 - (key: platform.languageSet.first?.productVersion ?? "", value: platform) - }.sorted { pair1, pair2 in - // 按版本号降序排序 - AppStatics.compareVersions(pair1.key, pair2.key) > 0 + for product in products { + let platforms = product.platforms.filter { platform in + StorageData.shared.allowedPlatform.contains(platform.id) + } + + if let firstPlatform = platforms.first { + versionPlatformMap[product.version] = firstPlatform + } } + + return versionPlatformMap.map { (key: $0.key, value: $0.value) } + .sorted { pair1, pair2 in + AppStatics.compareVersions(pair1.key, pair2.key) > 0 + } } private func handleVersionSelect(_ version: String) { @@ -150,7 +189,7 @@ private struct VersionListView: View { private struct VersionRow: View { @StorageValue(\.defaultLanguage) private var defaultLanguage - let product: Product + let productId: String let version: String let info: Product.Platform let isExpanded: Bool @@ -159,7 +198,7 @@ private struct VersionRow: View { private var existingPath: URL? { globalNetworkManager.isVersionDownloaded( - productId: product.id, + productId: productId, version: version, language: defaultLanguage ) @@ -172,7 +211,7 @@ private struct VersionRow: View { info: info, isExpanded: isExpanded, hasExistingPath: existingPath != nil, - onSelect: handleSelect, + onSelect: { onToggle(version) }, onToggle: { onToggle(version) } ) @@ -188,15 +227,6 @@ private struct VersionRow: View { .background(Color(NSColor.controlBackgroundColor)) .cornerRadius(VersionPickerConstants.cornerRadius) } - - private func handleSelect() { - let dependencies = info.languageSet.first?.dependencies ?? [] - if dependencies.isEmpty { - onSelect(version) - } else { - onToggle(version) - } - } } private struct VersionHeader: View { @@ -207,16 +237,20 @@ private struct VersionHeader: View { let onSelect: () -> Void let onToggle: () -> Void + private var hasDependencies: Bool { + !(info.languageSet.first?.dependencies.isEmpty ?? true) + } + var body: some View { Button(action: onSelect) { HStack { - VersionInfo(version: version, platform: info.id) + VersionInfo(version: version, platform: info.id, info: info) Spacer() ExistingPathButton(isVisible: hasExistingPath) ExpandButton( isExpanded: isExpanded, onToggle: onToggle, - hasDependencies: !(info.languageSet.first?.dependencies.isEmpty ?? true) + hasDependencies: hasDependencies ) } .padding(.vertical, VersionPickerConstants.buttonPadding) @@ -229,14 +263,46 @@ private struct VersionHeader: View { private struct VersionInfo: View { let version: String let platform: String + let info: Product.Platform + + private var productVersion: String? { + info.languageSet.first?.productVersion + } + + private var buildGuid: String? { + info.languageSet.first?.buildGuid + } var body: some View { VStack(alignment: .leading, spacing: 4) { - Text(version) - .font(.headline) - Text(platform) - .font(.caption) - .foregroundColor(.secondary) + HStack(spacing: 6) { + Text(version) + .font(.headline) + + if let pv = productVersion, pv != version { + Text("•") + .foregroundColor(.secondary) + Text("v\(pv)") + .font(.caption) + .foregroundColor(.blue) + } + } + + HStack(spacing: 4) { + Text(platform) + .font(.caption) + .foregroundColor(.secondary) + + if let guid = buildGuid { + Text("•") + .font(.caption) + .foregroundColor(.secondary) + Text(guid) + .font(.caption2) + .foregroundColor(.secondary) + .textSelection(.enabled) + } + } } } } @@ -263,17 +329,10 @@ private struct ExpandButton: View { let hasDependencies: Bool var body: some View { - Button(action: onToggle) { - Image(systemName: iconName) - .foregroundColor(.secondary) - } - } - - private var iconName: String { - if !hasDependencies { - return "chevron.right" - } - return isExpanded ? "chevron.down" : "chevron.right" + Image(systemName: isExpanded ? "chevron.down" : "chevron.right") + .foregroundColor(.secondary) + .contentShape(Rectangle()) + .onTapGesture(perform: onToggle) } } @@ -282,15 +341,56 @@ private struct VersionDetails: View { let version: String let onSelect: (String) -> Void + private var hasDependencies: Bool { + !(info.languageSet.first?.dependencies.isEmpty ?? true) + } + + private var hasModules: Bool { + !(info.modules.isEmpty) + } + var body: some View { VStack(alignment: .leading, spacing: VersionPickerConstants.verticalSpacing) { - Text("依赖包:") - .font(.caption) - .foregroundColor(.secondary) - .padding(.top, 8) - .padding(.leading, 16) - - DependenciesList(dependencies: info.languageSet.first?.dependencies ?? []) + if hasDependencies || hasModules { + VStack(alignment: .leading, spacing: 8) { + if hasDependencies { + HStack(spacing: 4) { + Image(systemName: "shippingbox") + .foregroundColor(.blue) + Text("依赖组件") + .font(.caption) + .foregroundColor(.secondary) + Text("(\(info.languageSet.first?.dependencies.count ?? 0))") + .font(.caption) + .foregroundColor(.blue) + } + DependenciesList(dependencies: info.languageSet.first?.dependencies ?? []) + .padding(.leading, 8) + } + + if hasModules { + if hasDependencies { + Divider() + .padding(.vertical, 4) + } + + HStack(spacing: 4) { + Image(systemName: "square.stack.3d.up") + .foregroundColor(.blue) + Text("可选模块") + .font(.caption) + .foregroundColor(.secondary) + Text("(\(info.modules.count))") + .font(.caption) + .foregroundColor(.blue) + } + ModulesList(modules: info.modules) + .padding(.leading, 8) + } + } + .background(Color(NSColor.controlBackgroundColor).opacity(0.5)) + .cornerRadius(6) + } DownloadButton(version: version, onSelect: onSelect) } @@ -304,15 +404,118 @@ private struct DependenciesList: View { var body: some View { ForEach(dependencies, id: \.sapCode) { dependency in - HStack(spacing: 8) { - Image(systemName: "cube.box") - .foregroundColor(.blue) - .frame(width: 16) - Text("\(dependency.sapCode) (\(dependency.baseVersion))") + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 6) { + getPlatformIcon(for: dependency.selectedPlatform) + .foregroundColor(.blue) + .frame(width: 16) + + Text(dependency.sapCode) + .font(.caption) + .fontWeight(.medium) + + Text("v\(dependency.productVersion)") + .font(.caption) + .foregroundColor(.blue) + } + + HStack(spacing: 8) { + if dependency.baseVersion != dependency.productVersion { + Text("base: \(dependency.baseVersion)") + .font(.caption2) + .foregroundColor(.secondary) + } + + if !dependency.buildGuid.isEmpty { + HStack(spacing: 4) { + Text("buildGuid:") + .font(.caption2) + .foregroundColor(.secondary) + Text(dependency.buildGuid) + .font(.caption2) + .foregroundColor(.secondary) + .textSelection(.enabled) + } + } + } + .padding(.leading, 22) + + // 第三行:调试信息(仅在 DEBUG 模式下显示) + #if DEBUG + VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 4) { + Text("Match:") + .font(.caption2) + .foregroundColor(.secondary) + Text(dependency.isMatchPlatform ? "✅" : "❌") + .font(.caption2) + + Text("•") + .font(.caption2) + .foregroundColor(.secondary) + + Text("Target:") + .font(.caption2) + .foregroundColor(.secondary) + Text(dependency.targetPlatform) + .font(.caption2) + .foregroundColor(.blue) + } + + if !dependency.selectedReason.isEmpty { + HStack(spacing: 4) { + Text("Reason:") + .font(.caption2) + .foregroundColor(.secondary) + Text(dependency.selectedReason) + .font(.caption2) + .foregroundColor(.orange) + } + } + } + .padding(.leading, 22) + #endif + } + .padding(.vertical, 4) + } + } + + private func getPlatformIcon(for platform: String) -> Image { + switch platform { + case "macarm64": + return Image(systemName: "m.square") + case "macuniversal": + return Image(systemName: "m.circle") + case "osx10", "osx10-64": + return Image(systemName: "x.square") + default: + return Image(systemName: "questionmark.square") + } + } +} + +private struct ModulesList: View { + let modules: [Product.Platform.Module] + + var body: some View { + ForEach(modules, id: \.id) { module in + HStack(spacing: 6) { + Circle() + .fill(Color.blue.opacity(0.2)) + .frame(width: 6, height: 6) + + Text(module.displayName) .font(.caption) + + if !module.deploymentType.isEmpty { + Text("(\(module.deploymentType))") + .font(.caption) + .foregroundColor(.secondary) + } + Spacer() } - .padding(.leading, 24) + .padding(.vertical, 2) } } } @@ -322,11 +525,10 @@ private struct DownloadButton: View { let onSelect: (String) -> Void var body: some View { - Button("下载此版本") { + Button("下载") { onSelect(version) } .buttonStyle(.borderedProminent) .padding(.top, 8) - .padding(.leading, 16) } } diff --git a/Localizables/Localizable.xcstrings b/Localizables/Localizable.xcstrings index 29557de..52e809c 100644 --- a/Localizables/Localizable.xcstrings +++ b/Localizables/Localizable.xcstrings @@ -3,14 +3,15 @@ "strings" : { "" : { - }, - " [%@]" : { - }, "(%@)" : { + }, + "(%lld)" : { + }, "(可能导致处理 Setup 组件失败)" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -21,6 +22,7 @@ } }, "(将导致无法使用安装功能)" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -29,6 +31,12 @@ } } } + }, + "(退出代码: %lld)" : { + + }, + "[%@]" : { + }, "/" : { @@ -126,6 +134,9 @@ } } } + }, + "•" : { + }, "• 错误 2700:不太可能会出现,除非 Setup 组件处理失败了\n• 错误 107:所下载的文件架构与系统架构不一致或者安装文件被损坏\n• 错误 103:出现权限问题,请确保 Helper 状态正常\n• 错误 182:文件不齐全或文件被损坏,或者你的Setup组件不一致,请重新下载 X1a0He CC\n• 错误 133:系统磁盘空间不足\n• 错误 -1:Setup 组件未处理或处理失败,请联系开发者\n• 错误 195:所下载的产品不支持你当前的系统\n• 错误 146:请在 Mac 系统设置中给予 Adobe Downloader 全磁盘权限\n• 错误 255:Setup 组件需要更新,请联系开发者解决" : { "localizations" : { @@ -137,10 +148,14 @@ } } }, - "|" : { + "✅" : { + + }, + "❌" : { }, "🔔 即将下载 %@ (%@) 版本 🔔" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -285,12 +300,17 @@ }, "Apple Silicon" : { + }, + "base: %@" : { + + }, + "buildGuid:" : { + }, "By X1a0He. ❤️ Love from China. 🇨🇳" : { }, "Debug 模式" : { - "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -420,9 +440,21 @@ } } } + }, + "Intel" : { + + }, + "macOS %@" : { + + }, + "Match:" : { + }, "OK" : { + }, + "Reason:" : { + }, "Setup 组件安装成功" : { "extractionState" : "stale", @@ -454,6 +486,9 @@ } } } + }, + "Setup 组件未被处理 (退出代码: %lld)" : { + }, "Setup未备份提示" : { "localizations" : { @@ -464,6 +499,12 @@ } } } + }, + "Target:" : { + + }, + "v%@" : { + }, "v4" : { @@ -478,6 +519,7 @@ }, "X1a0He CC 下载并处理成功" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -488,6 +530,7 @@ } }, "X1a0He CC 下载成功" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -640,6 +683,7 @@ }, "下载此版本" : { "comment" : "版本选择页面", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -837,6 +881,7 @@ } }, "依赖包:" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -847,6 +892,7 @@ } }, "依赖包: %lld" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -855,6 +901,9 @@ } } } + }, + "依赖组件" : { + }, "修改 Setup 组件失败" : { "localizations" : { @@ -1088,6 +1137,7 @@ } }, "可用版本: %lld" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1116,6 +1166,9 @@ } } } + }, + "可选模块" : { + }, "命令行安装" : { "localizations" : { @@ -1270,6 +1323,7 @@ } }, "安装失败 (退出代码: %lld)" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1290,6 +1344,7 @@ } }, "安装失败: Setup 组件未被处理 (退出代码: %lld)" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1300,6 +1355,7 @@ } }, "安装失败: 安装文件不完整或损坏 (退出代码: %lld)" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1310,6 +1366,7 @@ } }, "安装失败: 权限问题 (退出代码: %lld)" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1320,6 +1377,7 @@ } }, "安装失败: 架构或版本不一致 (退出代码: %lld)" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1348,6 +1406,9 @@ } } } + }, + "安装文件不完整或损坏 (退出代码: %lld)" : { + }, "安装程序已存在" : { "localizations" : { @@ -1392,7 +1453,6 @@ } }, "将执行的命令:" : { - "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1433,7 +1493,6 @@ } }, "展开全部" : { - "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1454,6 +1513,7 @@ } }, "已处理" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1464,6 +1524,7 @@ } }, "已备份" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1669,7 +1730,6 @@ } }, "折叠全部" : { - "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -2107,6 +2167,15 @@ } } } + }, + "权限问题 (退出代码: %lld)" : { + + }, + "架构或版本不一致 (退出代码: %lld)" : { + + }, + "查看持久化文件" : { + }, "检查中" : { "localizations" : { @@ -2672,6 +2741,9 @@ } } } + }, + "系统信息" : { + }, "继续" : { "localizations" : { @@ -2745,6 +2817,9 @@ } } } + }, + "获取到 %lld 个版本" : { + }, "获取到 %lld 款产品" : { "localizations" : {