diff --git a/Adobe Downloader.xcodeproj/project.pbxproj b/Adobe Downloader.xcodeproj/project.pbxproj index 0a0c608..e963fc7 100644 --- a/Adobe Downloader.xcodeproj/project.pbxproj +++ b/Adobe Downloader.xcodeproj/project.pbxproj @@ -6,13 +6,30 @@ objectVersion = 77; objects = { +/* Begin PBXBuildFile section */ + 3CB9FF092CDBAEF200D7A58B /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 3CB9FF082CDBAEF200D7A58B /* Sparkle */; }; +/* End PBXBuildFile section */ + /* Begin PBXFileReference section */ 3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Adobe Downloader.app"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 3CB9FF102CDC44DE00D7A58B /* Exceptions for "Adobe Downloader" folder in "Adobe Downloader" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 3CCC3ADF2CC67B8F006E22B4 /* Adobe Downloader */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + /* Begin PBXFileSystemSynchronizedRootGroup section */ 3CCC3AE22CC67B8F006E22B4 /* Adobe Downloader */ = { isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 3CB9FF102CDC44DE00D7A58B /* Exceptions for "Adobe Downloader" folder in "Adobe Downloader" target */, + ); path = "Adobe Downloader"; sourceTree = ""; }; @@ -28,6 +45,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3CB9FF092CDBAEF200D7A58B /* Sparkle in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -72,6 +90,7 @@ ); name = "Adobe Downloader"; packageProductDependencies = ( + 3CB9FF082CDBAEF200D7A58B /* Sparkle */, ); productName = "Adobe-Downloader"; productReference = 3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */; @@ -101,6 +120,9 @@ ); mainGroup = 3CCC3AD72CC67B8F006E22B4; minimizedProjectReferenceProxies = 1; + packageReferences = ( + 3CB9FF072CDBAEF200D7A58B /* XCRemoteSwiftPackageReference "Sparkle" */, + ); preferredProjectObjectVersion = 77; productRefGroup = 3CCC3AE12CC67B8F006E22B4 /* Products */; projectDirPath = ""; @@ -185,6 +207,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "./Adobe Downloader/Info.plist"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; @@ -243,6 +266,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "./Adobe Downloader/Info.plist"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; @@ -262,7 +286,7 @@ CODE_SIGN_ENTITLEMENTS = "Adobe Downloader/Adobe Downloader.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 101; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Adobe Downloader/Preview Content\""; DEVELOPMENT_TEAM = ""; @@ -275,8 +299,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = "com.x1a0he.macOS.Adobe-Downloader"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -291,9 +315,10 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = "Adobe Downloader/Adobe Downloader.entitlements"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 101; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Adobe Downloader/Preview Content\""; DEVELOPMENT_TEAM = ""; @@ -306,8 +331,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = "com.x1a0he.macOS.Adobe-Downloader"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -337,6 +362,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 3CB9FF072CDBAEF200D7A58B /* XCRemoteSwiftPackageReference "Sparkle" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sparkle-project/Sparkle"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.6.4; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 3CB9FF082CDBAEF200D7A58B /* Sparkle */ = { + isa = XCSwiftPackageProductDependency; + package = 3CB9FF072CDBAEF200D7A58B /* XCRemoteSwiftPackageReference "Sparkle" */; + productName = Sparkle; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 3CCC3AD82CC67B8F006E22B4 /* Project object */; } diff --git a/Adobe Downloader.xcodeproj/xcshareddata/xcschemes/Adobe Downloader.xcscheme b/Adobe Downloader.xcodeproj/xcshareddata/xcschemes/Adobe Downloader.xcscheme index 76e985d..4ef56ca 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"> - - com.apple.security.network.client - - - - com.apple.security.network.server - - - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - - - NSDownloadsFolderUsageDescription - 需要访问下载文件夹来保存Adobe安装文件 - - - com.apple.security.app-sandbox - - - com.apple.security.files.downloads.read-write - - - com.apple.security.files.user-selected.read-write - - - com.apple.security.temporary-exception.files.home-relative-path.read-write - - /Downloads/ - + com.apple.security.network.client + + com.apple.security.network.server + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSDownloadsFolderUsageDescription + 需要访问下载文件夹来保存Adobe安装文件 + com.apple.security.app-sandbox + + com.apple.security.files.downloads.read-write + + com.apple.security.files.user-selected.read-write + + com.apple.security.temporary-exception.files.home-relative-path.read-write + + /Downloads/ + + SUPublicEDKey + gYylad3ybfiyK5ZTS3xRrw+3c/8063mpXdQnPpMB86Q= + SUFeedURL + https://raw.githubusercontent.com/X1a0He/Adobe-Downloader/refs/heads/main/appcast.xml diff --git a/Adobe Downloader/Models/CheckForUpdatesViewModel.swift b/Adobe Downloader/Models/CheckForUpdatesViewModel.swift new file mode 100644 index 0000000..24ce0b6 --- /dev/null +++ b/Adobe Downloader/Models/CheckForUpdatesViewModel.swift @@ -0,0 +1,34 @@ +// +// CheckForUpdatesViewModel.swift +// Adobe Downloader +// +// Created by X1a0He on 11/6/24. +// + +import SwiftUI +import Sparkle + +final class CheckForUpdatesViewModel: ObservableObject { + @Published var canCheckForUpdates = false + + init(updater: SPUUpdater) { + updater.publisher(for: \.canCheckForUpdates) + .assign(to: &$canCheckForUpdates) + } +} + +struct CheckForUpdatesView: View { + @ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel + private let updater: SPUUpdater + + init(updater: SPUUpdater) { + self.updater = updater + + self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater) + } + + var body: some View { + Button("检查更新...", action: updater.checkForUpdates) + .disabled(!checkForUpdatesViewModel.canCheckForUpdates) + } +} diff --git a/Adobe Downloader/NetworkManager.swift b/Adobe Downloader/NetworkManager.swift index d79c83e..44ba145 100644 --- a/Adobe Downloader/NetworkManager.swift +++ b/Adobe Downloader/NetworkManager.swift @@ -22,6 +22,7 @@ class NetworkManager: ObservableObject { } } } + @Published var installCommand: String = "" private let cancelTracker = CancelTracker() internal var downloadUtils: DownloadUtils! internal var progressObservers: [UUID: NSKeyValueObservation] = [:] @@ -72,6 +73,8 @@ class NetworkManager: ObservableObject { downloadTasks.append(task) updateDockBadge() + NotificationCenter.default.post(name: NSNotification.Name("UpdateDownloadStatus"), object: nil) + do { try await downloadUtils.handleDownload(task: task, productInfo: productInfo, allowedPlatform: allowedPlatform, saps: saps) } catch { @@ -190,7 +193,13 @@ class NetworkManager: ObservableObject { installationState = .completed } } catch { + let command = await installManager.getInstallCommand( + for: path.appendingPathComponent("driver.xml").path + ) + await MainActor.run { + self.installCommand = command + if let installError = error as? InstallManager.InstallError { switch installError { case .installationFailed(let message): diff --git a/Adobe Downloader/Utils/DownloadUtils.swift b/Adobe Downloader/Utils/DownloadUtils.swift index 64d9785..0645440 100644 --- a/Adobe Downloader/Utils/DownloadUtils.swift +++ b/Adobe Downloader/Utils/DownloadUtils.swift @@ -365,7 +365,7 @@ class DownloadUtils { package.progress = 1.0 package.status = .completed package.downloaded = true - + var totalDownloaded: Int64 = 0 var totalSize: Int64 = 0 @@ -395,7 +395,6 @@ class DownloadUtils { ))) } - task.objectWillChange.send() networkManager?.objectWillChange.send() } @@ -437,7 +436,6 @@ class DownloadUtils { lastUpdateTime = now lastBytes = totalBytesWritten - task.objectWillChange.send() networkManager?.objectWillChange.send() } } @@ -677,7 +675,7 @@ class DownloadUtils { for product in productsToDownload { await MainActor.run { task.setStatus(.preparing(DownloadStatus.PrepareInfo( - message: "正在处理 \(product.sapCode) 的包信息...", + message: String(localized: "正在处理 \(product.sapCode) 的包信息..."), timestamp: Date(), stage: .fetchingInfo ))) diff --git a/Adobe Downloader/Utils/InstallManager.swift b/Adobe Downloader/Utils/InstallManager.swift index 4f072ef..eeaf2b9 100644 --- a/Adobe Downloader/Utils/InstallManager.swift +++ b/Adobe Downloader/Utils/InstallManager.swift @@ -20,10 +20,10 @@ actor InstallManager { var errorDescription: String? { switch self { - case .setupNotFound: return "找不到安装程序" - case .installationFailed(let message): return message - case .cancelled: return "安装已取消" - case .permissionDenied: return "权限被拒绝" + case .setupNotFound: return String(localized: "找不到安装程序") + case .installationFailed(let message): return message + case .cancelled: return String(localized: "安装已取消") + case .permissionDenied: return String(localized: "权限被拒绝") } } } @@ -72,7 +72,7 @@ actor InstallManager { installationProcess = installProcess await MainActor.run { - progressHandler(0.0, withSudo ? "正在准备安装..." : "正在重试安装...") + progressHandler(0.0, withSudo ? String(localized: "正在准备安装...") : String(localized: "正在重试安装...")) } try installProcess.run() @@ -93,10 +93,10 @@ actor InstallManager { let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces), let code = Int32(codeStr) { if code != 0 { - let errorMessage = code == -1 - ? "安装程序调用失败,请联系X1a0He" - : "(退出代码: \(code))" - + let errorMessage = code == -1 + ? String(localized: "安装程序调用失败,请联系X1a0He") + : String(localized: "(退出代码: \(code))") + installProcess.terminate() continuation.resume(throwing: InstallError.installationFailed(errorMessage)) return @@ -115,9 +115,9 @@ actor InstallManager { if installProcess.terminationStatus == 0 { continuation.resume() } else { - let errorMessage = withSudo - ? "安装失败 (退出代码: \(installProcess.terminationStatus))" - : "重试失败,需要重新输入密码" + let errorMessage = withSudo + ? String(localized: "(退出代码: \(installProcess.terminationStatus))") + : String(localized: "重试失败,需要重新输入密码") continuation.resume(throwing: InstallError.installationFailed(errorMessage)) } } catch { @@ -127,7 +127,7 @@ actor InstallManager { } await MainActor.run { - progressHandler(1.0, "安装完成") + progressHandler(1.0, String(localized: "安装完成")) } } @@ -167,7 +167,7 @@ actor InstallManager { authProcess.executableURL = URL(fileURLWithPath: "/usr/bin/osascript") let authScript = """ tell application "System Events" - display dialog "请输入管理员密码以继续安装" default answer "" with hidden answer ¬ + display dialog "请输入管理员密码以继续安装(Please enter the password to continue the installation)" default answer "" with hidden answer ¬ buttons {"取消", "确定"} default button "确定" ¬ with icon caution ¬ with title "需要管理员权限" @@ -208,22 +208,26 @@ actor InstallManager { if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression), let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces), let exitCode = Int(codeStr) { - return exitCode == 0 ? (1.0, "安装完成") : nil + return exitCode == 0 ? (1.0, String(localized: "安装完成")) : nil } if let range = line.range(of: "Progress: ([0-9]{1,3})%", options: .regularExpression), let progressStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces), let progressValue = Double(progressStr.replacingOccurrences(of: "%", with: "")) { - return (progressValue / 100.0, "正在安装...") + return (progressValue / 100.0, String("正在安装...")) } if line.contains("Installing packages") { - return (0.0, "正在安装包...") + return (0.0, String(localized: "正在安装包...")) } else if line.contains("Preparing") { - return (0.0, "正在准备...") + return (0.0, String(localized: "正在准备...")) } return nil } + + func getInstallCommand(for driverPath: String) -> String { + return "sudo \"\(setupPath)\" --install=1 --driverXML=\"\(driverPath)\"" + } } diff --git a/Adobe Downloader/Views/AboutView.swift b/Adobe Downloader/Views/AboutView.swift index bd6fbd0..274339a 100644 --- a/Adobe Downloader/Views/AboutView.swift +++ b/Adobe Downloader/Views/AboutView.swift @@ -5,11 +5,18 @@ // import SwiftUI +import Sparkle struct AboutView: View { + private let updater: SPUUpdater + + init(updater: SPUUpdater) { + self.updater = updater + } + var body: some View { TabView { - GeneralSettingsView() + GeneralSettingsView(updater: updater) .tabItem { Label("通用", systemImage: "gear") } @@ -19,7 +26,8 @@ struct AboutView: View { Label("关于", systemImage: "info.circle") } } - .frame(width: 500, height: 400) + .background(Color(NSColor.windowBackgroundColor)) + .frame(width: 500, height: 350) } } @@ -32,7 +40,17 @@ struct GeneralSettingsView: View { @AppStorage("downloadAppleSilicon") private var downloadAppleSilicon: Bool = true @State private var showLanguagePicker = false @EnvironmentObject private var networkManager: NetworkManager - + + private let updater: SPUUpdater + @State private var automaticallyChecksForUpdates: Bool + @State private var automaticallyDownloadsUpdates: Bool + + init(updater: SPUUpdater) { + self.updater = updater + _automaticallyChecksForUpdates = State(initialValue: updater.automaticallyChecksForUpdates) + _automaticallyDownloadsUpdates = State(initialValue: updater.automaticallyDownloadsUpdates) + } + var body: some View { Form { GroupBox(label: Text("下载设置").padding(.bottom, 8)) { @@ -86,8 +104,29 @@ struct GeneralSettingsView: View { networkManager.updateAllowedPlatform(useAppleSilicon: newValue) } } - .padding(.vertical, 4) + .padding(8) } + + GroupBox(label: Text("更新设置").padding(.bottom, 8)) { + VStack(alignment: .leading, spacing: 12) { + HStack { + Toggle("自动检查更新版本", isOn: $automaticallyChecksForUpdates) + .onChange(of: automaticallyChecksForUpdates) { newValue in + updater.automaticallyChecksForUpdates = newValue + } + Spacer() + + CheckForUpdatesView(updater: updater) + } + Divider() + Toggle("自动下载最新版本", isOn: $automaticallyDownloadsUpdates) + .disabled(!automaticallyChecksForUpdates) + .onChange(of: automaticallyDownloadsUpdates) { newValue in + updater.automaticallyDownloadsUpdates = newValue + } + }.padding(8) + } + .padding(.vertical, 5) } .padding() .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) @@ -126,13 +165,20 @@ struct GeneralSettingsView: View { } struct AboutAppView: View { + 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)" + } + var body: some View { VStack(spacing: 12) { Image(nsImage: NSApp.applicationIconImage) .resizable() .frame(width: 96, height: 96) - Text("Adobe Downloader") + Text("Adobe Downloader \(appVersion)") .font(.title2) .bold() @@ -168,8 +214,39 @@ struct AboutAppView: View { } } -#Preview { +#Preview("About Tab") { + AboutAppView() +} + +#Preview("General Settings") { let networkManager = NetworkManager() - return AboutView() - .environmentObject(networkManager) + 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) + + super.init( + hostBundle: hostBundle, + applicationBundle: applicationBundle, + userDriver: userDriver, + delegate: nil + ) + } + + override var automaticallyChecksForUpdates: Bool { + get { true } + set { } + } + + override var automaticallyDownloadsUpdates: Bool { + get { true } + set { } + } } diff --git a/Adobe Downloader/Views/AppCardView.swift b/Adobe Downloader/Views/AppCardView.swift index 11e0cee..60bae13 100644 --- a/Adobe Downloader/Views/AppCardView.swift +++ b/Adobe Downloader/Views/AppCardView.swift @@ -51,22 +51,24 @@ class AppCardViewModel: ObservableObject { self.sap = sap self.networkManager = networkManager loadIcon() - } - - func updateDownloadingStatus() { - Task { @MainActor in - isDownloading = networkManager?.downloadTasks.contains(where: isTaskDownloading) ?? false - } - } - - private func isTaskDownloading(_ task: NewDownloadTask) -> Bool { - guard task.sapCode == sap.sapCode else { return false } - switch task.totalStatus { - case .downloading, .preparing, .waiting, .retrying: - return true - default: - return false + NotificationCenter.default.addObserver( + self, + selector: #selector(updateDownloadingStatus), + name: NSNotification.Name("UpdateDownloadStatus"), + object: nil + ) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + @objc func updateDownloadingStatus() { + Task { @MainActor in + isDownloading = networkManager?.downloadTasks.contains { task in + return task.sapCode == sap.sapCode && task.status.isActive + } ?? false } } @@ -203,6 +205,17 @@ class AppCardViewModel: ObservableObject { guard let networkManager = networkManager, let productInfo = sap.versions[pendingVersion] else { return } + let existingTask = await networkManager.downloadTasks.first { task in + return task.sapCode == sap.sapCode && + task.version == pendingVersion && + task.language == pendingLanguage && + task.directory == path + } + + if existingTask != nil { + return + } + var productsToDownload: [ProductsToDownload] = [] let mainProduct = ProductsToDownload( sapCode: sap.sapCode, @@ -446,38 +459,38 @@ private struct DownloadButton: View { struct AlertModifier: ViewModifier { @ObservedObject var viewModel: AppCardViewModel let confirmRedownload: Bool - + func body(content: Content) -> some View { content - .alert("安装程序已存在", isPresented: $viewModel.showExistingFileAlert) { - Button("使用现有程序") { - if let path = viewModel.existingFilePath, - !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty { - Task { - await viewModel.createCompletedTask(path) - } - } - } - Button("重新下载") { - if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty { - if confirmRedownload { - viewModel.showRedownloadConfirm = true - } else { - viewModel.startDownload(viewModel.pendingVersion, viewModel.pendingLanguage) - } - } - } - Button("取消", role: .cancel) {} - } message: { - VStack(alignment: .leading) { - Text("在以下位置找到现有的安装程序:") - if let path = viewModel.existingFilePath { - Text(path.path) - .foregroundColor(.blue) - .onTapGesture { - NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) + .sheet(isPresented: $viewModel.showExistingFileAlert) { + if let path = viewModel.existingFilePath { + ExistingFileAlertView( + path: path, + onUseExisting: { + viewModel.showExistingFileAlert = false + if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty { + Task { + await viewModel.createCompletedTask(path) + } } - } + }, + onRedownload: { + viewModel.showExistingFileAlert = false + if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty { + if confirmRedownload { + viewModel.showRedownloadConfirm = true + } else { + viewModel.startDownload(viewModel.pendingVersion, viewModel.pendingLanguage) + } + } + }, + onCancel: { + viewModel.showExistingFileAlert = false + }, + iconImage: viewModel.iconImage + ) + .background(Color.black.opacity(0.3)) + .ignoresSafeArea() } } .alert("确认重新下载", isPresented: $viewModel.showRedownloadConfirm) { diff --git a/Adobe Downloader/Views/DownloadManagerView.swift b/Adobe Downloader/Views/DownloadManagerView.swift index e27ffc1..4ff5b60 100644 --- a/Adobe Downloader/Views/DownloadManagerView.swift +++ b/Adobe Downloader/Views/DownloadManagerView.swift @@ -112,6 +112,8 @@ struct DownloadManagerView: View { Button("关闭") { dismiss() } + .buttonStyle(.borderedProminent) + .tint(.red) } .padding(.horizontal) .padding(.vertical, 8) diff --git a/Adobe Downloader/Views/DownloadProgressView.swift b/Adobe Downloader/Views/DownloadProgressView.swift index cd7fea0..c985f86 100644 --- a/Adobe Downloader/Views/DownloadProgressView.swift +++ b/Adobe Downloader/Views/DownloadProgressView.swift @@ -443,10 +443,6 @@ struct ProductRow: View { let isCurrentProduct: Bool @Binding var expandedProducts: Set - private var completedPackagesCount: Int { - product.packages.filter(\.downloaded).count - } - var body: some View { VStack(alignment: .leading, spacing: 4) { Button(action: { @@ -467,7 +463,7 @@ struct ProductRow: View { Spacer() - Text("\(completedPackagesCount)/\(product.packages.count)") + Text("\(product.completedPackages)/\(product.totalPackages)") .font(.caption) .foregroundColor(.secondary) diff --git a/Adobe Downloader/Views/ExistingFileAlertView.swift b/Adobe Downloader/Views/ExistingFileAlertView.swift new file mode 100644 index 0000000..f2a8b22 --- /dev/null +++ b/Adobe Downloader/Views/ExistingFileAlertView.swift @@ -0,0 +1,106 @@ +import SwiftUI + +struct ExistingFileAlertView: View { + let path: URL + let onUseExisting: () -> Void + let onRedownload: () -> Void + let onCancel: () -> Void + let iconImage: NSImage? + + var body: some View { + VStack(spacing: 20) { + ZStack(alignment: .bottomTrailing) { + Group { + if let iconImage = iconImage { + Image(nsImage: iconImage) + .resizable() + .interpolation(.high) + .scaledToFit() + } else { + Image(systemName: "app.fill") + .resizable() + .scaledToFit() + .foregroundColor(.secondary) + } + } + .frame(width: 64, height: 64) + + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 24)) + .foregroundColor(.orange) + .offset(x: 10, y: 4) + } + .padding(.bottom, 5) + + Text("安装程序已存在") + .font(.headline) + + VStack(alignment: .leading, spacing: 8) { + HStack { + Text(path.path) + .foregroundColor(.blue) + .onTapGesture { + NSWorkspace.shared.activateFileViewerSelecting([path]) + } + } + } + + VStack(spacing: 16) { + Button(action: onUseExisting) { + Label("使用现有程序", systemImage: "checkmark.circle") + .frame(minWidth: 0,maxWidth: 260) + .frame(height: 32) + .font(.system(size: 14)) + } + .buttonStyle(.borderedProminent) + .tint(.blue) + + Button(action: onRedownload) { + Label("重新下载", systemImage: "arrow.down.circle") + .frame(minWidth: 0,maxWidth: 260) + .frame(height: 32) + .font(.system(size: 14)) + } + .buttonStyle(.borderedProminent) + .tint(.green) + + Button(action: onCancel) { + Label("取消", systemImage: "xmark.circle") + .frame(minWidth: 0, maxWidth: 260) + .frame(height: 32) + .font(.system(size: 14)) + } + .buttonStyle(.borderedProminent) + .tint(.red) + .keyboardShortcut(.cancelAction) + } + } + .padding() + .background(Color(NSColor.windowBackgroundColor)) + .cornerRadius(12) + .shadow(radius: 10) + } +} + +#Preview { + ExistingFileAlertView( + path: URL(fileURLWithPath: "/Users/username/Downloads/Adobe/Adobe Downloader PHSP_25.0-en_US-macuniversal"), + onUseExisting: {}, + onRedownload: {}, + onCancel: {}, + iconImage: NSImage(named: "PHSP") + ) + .background(Color.black.opacity(0.3)) +} + +#Preview("Dark Mode") { + ExistingFileAlertView( + path: URL(fileURLWithPath: "/Users/username/Downloads/Adobe/Adobe Downloader PHSP_25.0-en_US-macuniversal"), + onUseExisting: {}, + onRedownload: {}, + onCancel: {}, + iconImage: NSImage(named: "PHSP") + ) + .background(Color.black.opacity(0.3)) + .preferredColorScheme(.dark) +} diff --git a/Adobe Downloader/Views/InstallProgressView.swift b/Adobe Downloader/Views/InstallProgressView.swift index 54058cb..b58d61a 100644 --- a/Adobe Downloader/Views/InstallProgressView.swift +++ b/Adobe Downloader/Views/InstallProgressView.swift @@ -14,16 +14,16 @@ struct InstallProgressView: View { let onRetry: (() -> Void)? private var isCompleted: Bool { - progress >= 1.0 || status == "安装完成" + progress >= 1.0 || status == String(localized: "安装完成") } private var isFailed: Bool { - status.contains("失败") + status.contains(String(localized: "失败")) } private var progressText: String { if isCompleted { - return "安装完成" + return String(localized: "安装完成") } else { return "\(Int(progress * 100))%" } @@ -51,11 +51,11 @@ struct InstallProgressView: View { private var statusTitle: String { if isCompleted { - return "\(productName) 安装完成" + return String(localized: "\(productName) 安装完成") } else if isFailed { - return "\(productName) 安装失败" + return String(localized: "\(productName) 安装失败") } else { - return "正在安装 \(productName)" + return String(localized: "正在安装 \(productName)") } } @@ -78,7 +78,9 @@ struct InstallProgressView: View { LogSection(logs: networkManager.installationLogs) if isFailed { - ErrorSection(status: status) + ErrorSection( + status: status, isFailed: isFailed + ) } ButtonSection( @@ -89,7 +91,7 @@ struct InstallProgressView: View { ) } .padding() - .frame(minWidth: 500, minHeight: 300) + .frame(minWidth: 500, minHeight: 400) .background(Color(NSColor.windowBackgroundColor)) .cornerRadius(8) } @@ -155,28 +157,61 @@ private struct LogSection: View { private struct ErrorSection: View { let status: String - + let isFailed: Bool + + var body: some View { + + VStack(alignment: .leading, spacing: 8) { + Text("错误详情:") + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.medium) + Text(status) + .font(.caption) + .foregroundColor(.secondary) + .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(8) + .background(Color.secondary.opacity(0.1)) + .cornerRadius(6) + if isFailed { + HStack { + Text("自行安装命令:") + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.medium) + CommandPopover() + } + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 20) + } +} + +private struct CommandSection: View { + let command: String + var body: some View { ScrollView { VStack(alignment: .leading, spacing: 8) { - Text("错误详情:") + Text("自行安装命令:") .font(.caption) .foregroundColor(.secondary) .fontWeight(.medium) - Text(status) + Text(command) .font(.caption) .foregroundColor(.secondary) .textSelection(.enabled) - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity,alignment: .leading) + .frame(minHeight: 200) .padding(8) .background(Color.secondary.opacity(0.1)) .cornerRadius(6) } .frame(maxWidth: .infinity, alignment: .leading) } - .frame(maxHeight: 100) - .padding(.horizontal, 20) } } @@ -220,6 +255,36 @@ private struct ButtonSection: View { } } +private struct CommandPopover: View { + @EnvironmentObject private var networkManager: NetworkManager + @State private var showPopover = false + + var body: some View { + Button(action: { showPopover.toggle() }) { + Image(systemName: "terminal.fill") + .foregroundColor(.secondary) + } + .buttonStyle(.plain) + .popover(isPresented: $showPopover, arrowEdge: .bottom) { + VStack(alignment: .leading, spacing: 8) { + Button("复制命令") { + + } + + Text(networkManager.installCommand) + .font(.system(.caption, design: .monospaced)) + .foregroundColor(.secondary) + .textSelection(.enabled) + .padding(8) + .background(Color.secondary.opacity(0.1)) + .cornerRadius(6) + } + .padding() + .frame(width: 400) + } + } +} + #Preview("安装中带日志") { let networkManager = NetworkManager() return InstallProgressView( @@ -260,6 +325,8 @@ private struct ButtonSection: View { ) .environmentObject(networkManager) .onAppear { + networkManager.installCommand = "sudo \"/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup\" --install=1 --driverXML=\"/Users/demo/Downloads/Adobe Photoshop/driver.xml\"" + let previewLogs = [ "正在准备安装...", "Progress: 10%", diff --git a/Localizables/Localizable.xcstrings b/Localizables/Localizable.xcstrings index fc2b3e7..a6ea78d 100644 --- a/Localizables/Localizable.xcstrings +++ b/Localizables/Localizable.xcstrings @@ -1,6 +1,16 @@ { "sourceLanguage" : "en", "strings" : { + "(退出代码: %d)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "(Exit Code: %d)" + } + } + } + }, "/" : { }, @@ -34,6 +44,26 @@ } } }, + "%@ 安装失败" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Install failed" + } + } + } + }, + "%@ 安装完成" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Install completed" + } + } + } + }, "%lld" : { }, @@ -72,6 +102,9 @@ }, "Adobe Downloader" : { + }, + "Adobe Downloader %@" : { + }, "Adobe Downloader 已为你默认设定如下值" : { "localizations" : { @@ -355,12 +388,22 @@ } } }, - "在以下位置找到现有的安装程序:" : { + "复制命令" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Locate the existing installer at:" + "value" : "Copy" + } + } + } + }, + "失败" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed" } } } @@ -385,6 +428,26 @@ } } }, + "安装完成" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Completed" + } + } + } + }, + "安装已取消" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancelled" + } + } + } + }, "安装程序已存在" : { "localizations" : { "en" : { @@ -395,6 +458,16 @@ } } }, + "安装程序调用失败,请联系X1a0He" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The installation program failed to call, please contact X1a0He" + } + } + } + }, "尝试使用不同的搜索关键词" : { "localizations" : { "en" : { @@ -476,6 +549,16 @@ } } }, + "找不到安装程序" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installer not found" + } + } + } + }, "按名称" : { "localizations" : { "en" : { @@ -556,6 +639,16 @@ } } }, + "更新设置" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Update settings" + } + } + } + }, "服务器响应无效" : { "comment" : "Invalid response", "localizations" : { @@ -604,6 +697,26 @@ } } }, + "权限被拒绝" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Permission denied" + } + } + } + }, + "检查更新..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Check for updates…" + } + } + } + }, "检测到Setup文件尚未备份,如果你需要安装程序,则Setup必须被处理,点击确定后你需要输入密码,Adobe Downloader将自动处理并备份为Setup.original" : { "localizations" : { "en" : { @@ -625,6 +738,26 @@ } } }, + "正在准备..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preparing..." + } + } + } + }, + "正在准备安装..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preparing..." + } + } + } + }, "正在加载..." : { "localizations" : { "en" : { @@ -635,6 +768,46 @@ } } }, + "正在处理 %@ 的包信息..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Processing package for %@..." + } + } + } + }, + "正在安装 %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installing %@" + } + } + } + }, + "正在安装包..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installing packages..." + } + } + } + }, + "正在重试安装..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retrying installation..." + } + } + } + }, "没有找到产品" : { "localizations" : { "en" : { @@ -769,6 +942,36 @@ } } }, + "自动下载最新版本" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatically download updates" + } + } + } + }, + "自动检查更新版本" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatically check for updates" + } + } + } + }, + "自行安装命令:" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Self-installation command:" + } + } + } + }, "语言:" : { "localizations" : { "en" : { @@ -779,6 +982,17 @@ } } }, + "请求超时,请检查网络连接后重试" : { + "comment" : "Network timeout", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Request timed out, please check the network connection and try again" + } + } + } + }, "选择" : { "localizations" : { "en" : { @@ -861,6 +1075,16 @@ } } }, + "重试失败,需要重新输入密码" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retry failed, need to re-enter password" + } + } + } + }, "错误详情:" : { "localizations" : { "en" : { diff --git a/appcast.xml b/appcast.xml new file mode 100644 index 0000000..9bc0490 --- /dev/null +++ b/appcast.xml @@ -0,0 +1,38 @@ + + + + Adobe Downloader + + 1.0.1 + Thu, 07 Nov 2024 16:06:42 +0800 + 101 + 1.0.1 + 13.0 + + ul{margin-top: 0;margin-bottom: 7;padding-left: 18;} +
Adobe Downloader 更新日志:
+
    +
  • 修复了当系统版本低于 macOS 14.6 时无法打开程序的问题,现已支持 macOS 13.0 以上
  • +
  • 增加 Sparkle 用于检测更新
  • +
  • 当默认目录为 未选择 时,将 下载 文件夹作为默认目录
  • +
  • 当通过 Adobe Downloader 安装遇到权限问题时,提供终端命令让用户自行安装
  • +
  • 调整了文件已存在的 UI 显示
  • +
+
+
Adobe Downloader Changes:
+
    +
  • Support macOS 13.0 and above
  • +
  • Added Sparkle for checking update
  • +
  • When the default directory is not selected, the Downloads folder will be used as the default directory
  • +
  • When installing via Adobe Downloader and encountering permission issues, provide terminal commands to allow users to
  • +
  • install by themselves
  • +
  • Adjusted the UI display of existing files
  • +
+ ]]>
+
+
+
\ No newline at end of file diff --git a/readme-en.md b/readme-en.md index e2f7d8c..9152eee 100644 --- a/readme-en.md +++ b/readme-en.md @@ -6,7 +6,7 @@ ## Before Use -**🍎Only for macOS 14+.** +**🍎Only for macOS 13.0+.** > **If you like Adobe Downloader, or it helps you, please Star🌟 it.** > @@ -21,21 +21,21 @@ > 4. ⚠️⚠️⚠️ **All Adobe apps in Adobe Downloader are from official Adobe channels and are not cracked versions.** > 5. ❌❌❌ **Do not use an external hard drive or any USB to store it, as this will cause permission issues, I do not have the patience to solve any about permission issues** +> 6. ❌❌❌ **Due to permission reasons, there may be problems with installation on hackintosh** ## 📔Latest Log - For historical update logs, please go to [Update Log](update-log.md) -- 2024-11-06 15:50 Update Log +- 2024-11-07 16:00 Update Log ```markdown -1. Added default configuration settings and prompts when the program is started for the first time -2. Added optional architecture downloads, please select in settings -3. Fixed the problem of version detection error \(only checks whether the file exists, not whether it is complete\) -4. Removed the language selection and directory selection on the main interface and moved them to settings -5. Added architecture prompts on the version selection page -6. Removed the installer mechanism, and now no installer will be generated -7. Added Adobe Creative Cloud installation detection, which cannot be used before installation +1. Support macOS 13.0 and above +2. Added Sparkle for checking update +3. When the default directory is not selected, the Downloads folder will be used as the default directory +4. When installing via Adobe Downloader and encountering permission issues, provide terminal commands to allow users to + install by themselves +5. Adjusted the UI display of existing files ``` ### Language friendly diff --git a/readme.md b/readme.md index 57292ae..bd13195 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ ## 使用须知 -**🍎仅支持 macOS 14+.** +**🍎仅支持 macOS 13.0+** > **如果你也喜欢 Adobe Downloader, 或者对你又帮助, 请 Star 仓库吧 🌟, 你的支持是我更新的动力** > @@ -20,21 +20,20 @@ 的 [adobe-packager](https://github.com/Drovosek01/adobe-packager) > 4. ⚠️⚠️⚠️ **Adobe Downloader 中的所有 Adobe 应用均来自 Adobe 官方渠道,并非破解版本。** > 5. ❌❌❌ **不要将下载目录设置为外接移动硬盘或者USB设备,这会导致出现权限问题,我并没有时间也没有耐心处理任何权限问题** +> 6. ❌❌❌ **由于权限原因,可能会在黑苹果上出现无法安装的问题** ## 📔 最新日志 - 更多关于 App 的更新日志,请查看 [Update Log](update-log.md) -- 2024-11-06 15:50 更新日志 +- 2024-11-07 16:00 更新日志 ```markdown -1. 增加程序首次启动时的默认配置设定与提示 -2. 增加可选架构下载,请在设置中进行选择 -3. 修复了版本已存在检测错误的问题 \(仅检测文件是否存在,并不会检测是否完整\) -4. 移除主界面的语言选择和目录选择,移动到了设置中 -5. 版本选择页面增加架构提示 -6. 移除了安装程序的机制,现在不会再生成安装程序 -7. 增加了Adobe Creative Cloud安装检测,未安装前无法使用 +1. 修复了当系统版本低于 macOS 14.6 时无法打开程序的问题,现已支持 macOS 13.0 以上 +2. 增加 Sparkle 用于检测更新 +3. 当默认目录为 未选择 时,将 下载 文件夹作为默认目录 +4. 当通过 Adobe Downloader 安装遇到权限问题时,提供终端命令让用户自行安装 +5. 调整了文件已存在的 UI 显示 ``` ### 语言支持 diff --git a/update-log.md b/update-log.md index 96ca127..45e8f9f 100644 --- a/update-log.md +++ b/update-log.md @@ -1,11 +1,30 @@ # Change Log +- 2024-11-07 16:00 更新日志 + +```markdown +1. 修复了当系统版本低于 macOS 14.6 时无法打开程序的问题,现已支持 macOS 13.0 以上 +2. 增加 Sparkle 用于检测更新 +3. 当默认目录为 未选择 时,将 下载 文件夹作为默认目录 +4. 当通过 Adobe Downloader 安装遇到权限问题时,提供终端命令让用户自行安装 +5. 调整了文件已存在的 UI 显示 + +==================== + +1. Support macOS 13.0 and above +2. Added Sparkle for checking update +3. When the default directory is not selected, the Downloads folder will be used as the default directory +4. When installing via Adobe Downloader and encountering permission issues, provide terminal commands to allow users to + install by themselves +5. Adjusted the UI display of existing files +``` + - 2024-11-06 15:50 更新日志 ```markdown 1. 增加程序首次启动时的默认配置设定与提示 2. 增加可选架构下载,请在设置中进行选择 -3. 修复了版本已存在检测错误的问题 \(仅检测文件是否存在,并不会检测是否完整\) +3. 修复了版本已存在检测错误的问题 (仅检测文件是否存在,并不会检测是否完整) 4. 移除主界面的语言选择和目录选择,移动到了设置中 5. 版本选择页面增加架构提示 6. 移除了安装程序的机制,现在不会再生成安装程序 @@ -15,7 +34,7 @@ 1. Added default configuration settings and prompts when the program is started for the first time 2. Added optional architecture downloads, please select in settings -3. Fixed the problem of version detection error \(only checks whether the file exists, not whether it is complete\) +3. Fixed the problem of version detection error (only checks whether the file exists, not whether it is complete) 4. Removed the language selection and directory selection on the main interface and moved them to settings 5. Added architecture prompts on the version selection page 6. Removed the installer mechanism, and now no installer will be generated