mirror of
https://github.com/X1a0He/Adobe-Downloader.git
synced 2025-11-25 03:14:57 +08:00
feat: 增加自定义包下载(未完善)
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
||||
@@ -190,6 +190,10 @@ class Package: Identifiable, ObservableObject, Codable {
|
||||
@Published var status: PackageStatus = .waiting
|
||||
@Published var downloaded: Bool = false
|
||||
|
||||
@Published var isSelected: Bool = false
|
||||
var isRequired: Bool = false
|
||||
var condition: String = ""
|
||||
|
||||
var lastUpdated: Date = Date()
|
||||
var lastRecordedSize: Int64 = 0
|
||||
var retryCount: Int = 0
|
||||
@@ -221,12 +225,18 @@ class Package: Identifiable, ObservableObject, Codable {
|
||||
}
|
||||
}
|
||||
|
||||
init(type: String, fullPackageName: String, downloadSize: Int64, downloadURL: String, packageVersion: String) {
|
||||
init(type: String, fullPackageName: String, downloadSize: Int64, downloadURL: String, packageVersion: String, condition: String = "", isRequired: Bool = false) {
|
||||
self.type = type
|
||||
self.fullPackageName = fullPackageName
|
||||
self.downloadSize = downloadSize
|
||||
self.downloadURL = downloadURL
|
||||
self.packageVersion = packageVersion
|
||||
self.condition = condition
|
||||
self.isRequired = isRequired
|
||||
self.isSelected = isRequired
|
||||
if !isRequired {
|
||||
self.isSelected = shouldBeSelectedByDefault
|
||||
}
|
||||
}
|
||||
|
||||
func updateProgress(downloadedSize: Int64, speed: Double) {
|
||||
@@ -264,6 +274,29 @@ class Package: Identifiable, ObservableObject, Codable {
|
||||
var hasValidSize: Bool {
|
||||
downloadSize > 0
|
||||
}
|
||||
|
||||
var shouldBeSelectedByDefault: Bool {
|
||||
let targetArchitecture = StorageData.shared.downloadAppleSilicon ? "arm64" : "x64"
|
||||
let language = StorageData.shared.defaultLanguage
|
||||
let isCore = type == "core"
|
||||
|
||||
if isCore {
|
||||
if condition.isEmpty {
|
||||
return true
|
||||
} else {
|
||||
if condition.contains("[OSArchitecture]==\(targetArchitecture)") {
|
||||
return true
|
||||
}
|
||||
if condition.contains("[installLanguage]==\(language)") || language == "ALL" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return condition.contains("[installLanguage]==\(language)") || language == "ALL"
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func updateStatus(_ status: PackageStatus) {
|
||||
Task { @MainActor in
|
||||
@@ -273,7 +306,7 @@ class Package: Identifiable, ObservableObject, Codable {
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, type, fullPackageName, downloadSize, downloadURL, packageVersion
|
||||
case id, type, fullPackageName, downloadSize, downloadURL, packageVersion, condition, isRequired
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
@@ -283,6 +316,9 @@ class Package: Identifiable, ObservableObject, Codable {
|
||||
try container.encode(fullPackageName, forKey: .fullPackageName)
|
||||
try container.encode(downloadSize, forKey: .downloadSize)
|
||||
try container.encode(downloadURL, forKey: .downloadURL)
|
||||
try container.encode(packageVersion, forKey: .packageVersion)
|
||||
try container.encode(condition, forKey: .condition)
|
||||
try container.encode(isRequired, forKey: .isRequired)
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
@@ -293,6 +329,12 @@ class Package: Identifiable, ObservableObject, Codable {
|
||||
downloadSize = try container.decode(Int64.self, forKey: .downloadSize)
|
||||
downloadURL = try container.decode(String.self, forKey: .downloadURL)
|
||||
packageVersion = try container.decode(String.self, forKey: .packageVersion)
|
||||
condition = try container.decodeIfPresent(String.self, forKey: .condition) ?? ""
|
||||
isRequired = try container.decodeIfPresent(Bool.self, forKey: .isRequired) ?? false
|
||||
isSelected = isRequired
|
||||
if !isRequired {
|
||||
isSelected = shouldBeSelectedByDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,50 @@ class NetworkManager: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startCustomDownload(productId: String, selectedVersion: String, language: String, destinationURL: URL, customDependencies: [DependenciesToDownload]) async throws {
|
||||
guard let productInfo = globalCcmResult.products.first(where: { $0.id == productId && $0.version == selectedVersion }) else {
|
||||
throw NetworkError.productNotFound
|
||||
}
|
||||
|
||||
let task = NewDownloadTask(
|
||||
productId: productInfo.id,
|
||||
productVersion: selectedVersion,
|
||||
language: language,
|
||||
displayName: productInfo.displayName,
|
||||
directory: destinationURL,
|
||||
dependenciesToDownload: [],
|
||||
createAt: Date(),
|
||||
totalStatus: .preparing(DownloadStatus.PrepareInfo(
|
||||
message: "正在准备自定义下载...",
|
||||
timestamp: Date(),
|
||||
stage: .initializing
|
||||
)),
|
||||
totalProgress: 0,
|
||||
totalDownloadedSize: 0,
|
||||
totalSize: 0,
|
||||
totalSpeed: 0,
|
||||
platform: globalProducts.first(where: { $0.id == productId })?.platforms.first?.id ?? "unknown")
|
||||
|
||||
downloadTasks.append(task)
|
||||
updateDockBadge()
|
||||
await saveTask(task)
|
||||
|
||||
do {
|
||||
try await globalNewDownloadUtils.handleCustomDownload(task: task, customDependencies: customDependencies)
|
||||
} catch {
|
||||
task.setStatus(.failed(DownloadStatus.FailureInfo(
|
||||
message: error.localizedDescription,
|
||||
error: error,
|
||||
timestamp: Date(),
|
||||
recoverable: true
|
||||
)))
|
||||
await saveTask(task)
|
||||
await MainActor.run {
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeTask(taskId: UUID, removeFiles: Bool = true) {
|
||||
Task {
|
||||
|
||||
@@ -287,7 +287,9 @@ class NewDownloadUtils {
|
||||
fullPackageName: fullPackageName,
|
||||
downloadSize: downloadSize,
|
||||
downloadURL: downloadURL,
|
||||
packageVersion: packageVersion
|
||||
packageVersion: packageVersion,
|
||||
condition: condition,
|
||||
isRequired: dependencyToDownload.sapCode == productInfo.id
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -333,6 +335,89 @@ class NewDownloadUtils {
|
||||
await startDownloadProcess(task: task)
|
||||
}
|
||||
|
||||
func handleCustomDownload(task: NewDownloadTask, customDependencies: [DependenciesToDownload]) async throws {
|
||||
await MainActor.run {
|
||||
task.setStatus(.preparing(DownloadStatus.PrepareInfo(
|
||||
message: String(localized: "正在准备自定义下载..."),
|
||||
timestamp: Date(),
|
||||
stage: .fetchingInfo
|
||||
)))
|
||||
}
|
||||
|
||||
for dependencyToDownload in customDependencies {
|
||||
let productDir = task.directory.appendingPathComponent("\(dependencyToDownload.sapCode)")
|
||||
if !FileManager.default.fileExists(atPath: productDir.path) {
|
||||
try FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
if let applicationJson = dependencyToDownload.applicationJson {
|
||||
var processedJsonString = applicationJson
|
||||
|
||||
if let jsonData = applicationJson.data(using: .utf8),
|
||||
var appInfo = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
|
||||
|
||||
let selectedPackageNames = Set(dependencyToDownload.packages.filter { $0.isSelected }.map { $0.fullPackageName })
|
||||
|
||||
if var packages = appInfo["Packages"] as? [String: Any],
|
||||
let packageArray = packages["Package"] as? [[String: Any]] {
|
||||
|
||||
let filteredPackages = packageArray.filter { package in
|
||||
if let packageName = package["PackageName"] as? String {
|
||||
let fullPackageName = packageName.hasSuffix(".zip") ? packageName : "\(packageName).zip"
|
||||
return selectedPackageNames.contains(fullPackageName)
|
||||
}
|
||||
if let fullPackageName = package["fullPackageName"] as? String {
|
||||
return selectedPackageNames.contains(fullPackageName)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
packages["Package"] = filteredPackages
|
||||
appInfo["Packages"] = packages
|
||||
}
|
||||
|
||||
if var modules = appInfo["Modules"] as? [String: Any] {
|
||||
modules["Module"] = [] as [[String: Any]]
|
||||
appInfo["Modules"] = modules
|
||||
}
|
||||
|
||||
if let processedData = try? JSONSerialization.data(withJSONObject: appInfo, options: .prettyPrinted),
|
||||
let processedString = String(data: processedData, encoding: .utf8) {
|
||||
processedJsonString = processedString
|
||||
}
|
||||
}
|
||||
|
||||
let jsonURL = productDir.appendingPathComponent("application.json")
|
||||
try processedJsonString.write(to: jsonURL, atomically: true, encoding: String.Encoding.utf8)
|
||||
}
|
||||
}
|
||||
|
||||
let filteredDependencies = customDependencies.map { dependency in
|
||||
let selectedPackages = dependency.packages.filter { $0.isSelected }
|
||||
let filteredDependency = DependenciesToDownload(
|
||||
sapCode: dependency.sapCode,
|
||||
version: dependency.version,
|
||||
buildGuid: dependency.buildGuid,
|
||||
applicationJson: dependency.applicationJson ?? ""
|
||||
)
|
||||
filteredDependency.packages = selectedPackages
|
||||
return filteredDependency
|
||||
}.filter { !$0.packages.isEmpty }
|
||||
|
||||
let totalSize = filteredDependencies.reduce(0) { productSum, product in
|
||||
productSum + product.packages.reduce(0) { packageSum, pkg in
|
||||
packageSum + (pkg.downloadSize > 0 ? pkg.downloadSize : 0)
|
||||
}
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
task.dependenciesToDownload = filteredDependencies
|
||||
task.totalSize = totalSize
|
||||
}
|
||||
|
||||
await startDownloadProcess(task: task)
|
||||
}
|
||||
|
||||
private func startDownloadProcess(task: NewDownloadTask) async {
|
||||
actor DownloadProgress {
|
||||
var currentPackageIndex: Int = 0
|
||||
|
||||
619
Adobe Downloader/Views/CustomDownloadView.swift
Normal file
619
Adobe Downloader/Views/CustomDownloadView.swift
Normal file
@@ -0,0 +1,619 @@
|
||||
//
|
||||
// CustomDownloadView.swift
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CustomDownloadView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@StateObject private var loadingState = CustomDownloadLoadingState()
|
||||
@State private var allPackages: [Package] = []
|
||||
@State private var dependenciesToDownload: [DependenciesToDownload] = []
|
||||
|
||||
let productId: String
|
||||
let version: String
|
||||
let onDownloadStart: ([DependenciesToDownload]) -> Void
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if loadingState.isLoading {
|
||||
CustomDownloadLoadingView(
|
||||
loadingState: loadingState,
|
||||
productId: productId,
|
||||
version: version
|
||||
)
|
||||
} else if loadingState.error != nil {
|
||||
VStack {
|
||||
Text("加载失败")
|
||||
.font(.headline)
|
||||
Text(loadingState.error!)
|
||||
.foregroundColor(.secondary)
|
||||
Button("重试") {
|
||||
loadingState.error = nil
|
||||
loadingState.isLoading = true
|
||||
loadPackageInfo()
|
||||
}
|
||||
.buttonStyle(BeautifulButtonStyle(baseColor: Color.blue))
|
||||
}
|
||||
.padding()
|
||||
} else {
|
||||
CustomPackageSelectorView(
|
||||
productId: productId,
|
||||
version: version,
|
||||
packages: allPackages,
|
||||
dependenciesToDownload: dependenciesToDownload,
|
||||
onDownloadStart: { dependencies in
|
||||
onDownloadStart(dependencies)
|
||||
dismiss()
|
||||
},
|
||||
onCancel: { dismiss() }
|
||||
)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if loadingState.isLoading {
|
||||
loadPackageInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadPackageInfo() {
|
||||
Task {
|
||||
do {
|
||||
let (packages, dependencies) = try await fetchPackageInfo()
|
||||
await MainActor.run {
|
||||
allPackages = packages
|
||||
dependenciesToDownload = dependencies
|
||||
loadingState.isLoading = false
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
loadingState.isLoading = false
|
||||
loadingState.error = error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchPackageInfo() async throws -> ([Package], [DependenciesToDownload]) {
|
||||
guard let product = findProduct(id: productId) else {
|
||||
throw NetworkError.invalidData("找不到产品信息")
|
||||
}
|
||||
|
||||
var allPackages: [Package] = []
|
||||
var dependenciesToDownload: [DependenciesToDownload] = []
|
||||
|
||||
let firstPlatform = product.platforms.first
|
||||
let buildGuid = firstPlatform?.languageSet.first?.buildGuid ?? ""
|
||||
|
||||
var dependencyInfos: [DependenciesToDownload] = []
|
||||
dependencyInfos.append(DependenciesToDownload(sapCode: product.id, version: product.version, buildGuid: buildGuid))
|
||||
|
||||
let dependencies = firstPlatform?.languageSet.first?.dependencies
|
||||
if let dependencies = dependencies {
|
||||
for dependency in dependencies {
|
||||
dependencyInfos.append(DependenciesToDownload(sapCode: dependency.sapCode, version: dependency.productVersion, buildGuid: dependency.buildGuid))
|
||||
}
|
||||
}
|
||||
|
||||
for dependencyInfo in dependencyInfos {
|
||||
await MainActor.run {
|
||||
loadingState.currentTask = "正在处理 \(dependencyInfo.sapCode) 的包信息..."
|
||||
}
|
||||
|
||||
let jsonString = try await globalNetworkService.getApplicationInfo(buildGuid: dependencyInfo.buildGuid)
|
||||
dependencyInfo.applicationJson = jsonString
|
||||
|
||||
var processedJsonString = jsonString
|
||||
if dependencyInfo.sapCode == product.id {
|
||||
if let jsonData = jsonString.data(using: .utf8),
|
||||
var appInfo = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
|
||||
|
||||
if var modules = appInfo["Modules"] as? [String: Any] {
|
||||
modules["Module"] = [] as [[String: Any]]
|
||||
appInfo["Modules"] = modules
|
||||
}
|
||||
|
||||
if let processedData = try? JSONSerialization.data(withJSONObject: appInfo, options: .prettyPrinted),
|
||||
let processedString = String(data: processedData, encoding: .utf8) {
|
||||
processedJsonString = processedString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let jsonData = processedJsonString.data(using: .utf8),
|
||||
let appInfo = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
|
||||
let packages = appInfo["Packages"] as? [String: Any],
|
||||
let packageArray = packages["Package"] as? [[String: Any]] else {
|
||||
throw NetworkError.invalidData("无法解析产品信息")
|
||||
}
|
||||
|
||||
for package in packageArray {
|
||||
guard let downloadURL = package["Path"] as? String, !downloadURL.isEmpty else { continue }
|
||||
|
||||
let packageVersion: String = package["PackageVersion"] as? String ?? ""
|
||||
let fullPackageName: String
|
||||
|
||||
if let name = package["fullPackageName"] as? String, !name.isEmpty {
|
||||
fullPackageName = name
|
||||
} else if let name = package["PackageName"] as? String, !name.isEmpty {
|
||||
fullPackageName = "\(name).zip"
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
let downloadSize: Int64
|
||||
switch package["DownloadSize"] {
|
||||
case let sizeNumber as NSNumber:
|
||||
downloadSize = sizeNumber.int64Value
|
||||
case let sizeString as String:
|
||||
downloadSize = Int64(sizeString) ?? 0
|
||||
default:
|
||||
downloadSize = 0
|
||||
}
|
||||
|
||||
let packageType = package["Type"] as? String ?? "non-core"
|
||||
let condition = package["Condition"] as? String ?? ""
|
||||
|
||||
// 判断是否应该默认选中这个包
|
||||
let isCore = packageType == "core"
|
||||
let targetArchitecture = StorageData.shared.downloadAppleSilicon ? "arm64" : "x64"
|
||||
let language = StorageData.shared.defaultLanguage
|
||||
let installLanguage = "[installLanguage]==\(language)"
|
||||
|
||||
var shouldDefaultSelect = false
|
||||
var isRequired = false
|
||||
|
||||
if dependencyInfo.sapCode == product.id {
|
||||
if isCore {
|
||||
shouldDefaultSelect = condition.isEmpty ||
|
||||
condition.contains("[OSArchitecture]==\(targetArchitecture)") ||
|
||||
condition.contains(installLanguage) || language == "ALL"
|
||||
// 主产品的core包且满足条件的设为必需
|
||||
isRequired = shouldDefaultSelect
|
||||
} else {
|
||||
shouldDefaultSelect = condition.contains(installLanguage) || language == "ALL"
|
||||
}
|
||||
} else {
|
||||
shouldDefaultSelect = condition.isEmpty ||
|
||||
(condition.contains("[OSVersion]") && checkOSVersionCondition(condition)) ||
|
||||
condition.contains(installLanguage) || language == "ALL"
|
||||
}
|
||||
|
||||
let packageObj = Package(
|
||||
type: packageType,
|
||||
fullPackageName: fullPackageName,
|
||||
downloadSize: downloadSize,
|
||||
downloadURL: downloadURL,
|
||||
packageVersion: packageVersion,
|
||||
condition: condition,
|
||||
isRequired: isRequired
|
||||
)
|
||||
|
||||
packageObj.isSelected = shouldDefaultSelect
|
||||
|
||||
dependencyInfo.packages.append(packageObj)
|
||||
allPackages.append(packageObj)
|
||||
}
|
||||
|
||||
dependenciesToDownload.append(dependencyInfo)
|
||||
}
|
||||
|
||||
return (allPackages, dependenciesToDownload)
|
||||
}
|
||||
|
||||
private func checkOSVersionCondition(_ condition: String) -> Bool {
|
||||
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
let currentVersion = Double("\(osVersion.majorVersion).\(osVersion.minorVersion)") ?? 0.0
|
||||
|
||||
let versionPattern = #"\[OSVersion\](>=|<=|<|>|==)([\d.]+)"#
|
||||
guard let regex = try? NSRegularExpression(pattern: versionPattern) else { return false }
|
||||
|
||||
let nsRange = NSRange(condition.startIndex..<condition.endIndex, in: condition)
|
||||
let matches = regex.matches(in: condition, range: nsRange)
|
||||
|
||||
for match in matches {
|
||||
guard match.numberOfRanges >= 3,
|
||||
let operatorRange = Range(match.range(at: 1), in: condition),
|
||||
let versionRange = Range(match.range(at: 2), in: condition),
|
||||
let requiredVersion = Double(condition[versionRange]) else { continue }
|
||||
|
||||
let operatorSymbol = String(condition[operatorRange])
|
||||
if !compareVersions(current: currentVersion, required: requiredVersion, operator: operatorSymbol) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return !matches.isEmpty
|
||||
}
|
||||
|
||||
private func compareVersions(current: Double, required: Double, operator: String) -> Bool {
|
||||
switch `operator` {
|
||||
case ">=":
|
||||
return current >= required
|
||||
case "<=":
|
||||
return current <= required
|
||||
case ">":
|
||||
return current > required
|
||||
case "<":
|
||||
return current < required
|
||||
case "==":
|
||||
return current == required
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomDownloadLoadingState: ObservableObject {
|
||||
@Published var isLoading = true
|
||||
@Published var currentTask = ""
|
||||
@Published var error: String?
|
||||
}
|
||||
|
||||
private struct CustomDownloadLoadingView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@ObservedObject var loadingState: CustomDownloadLoadingState
|
||||
|
||||
let productId: String
|
||||
let version: String
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
VStack {
|
||||
if let product = findProduct(id: productId) {
|
||||
HStack {
|
||||
if let icon = product.getBestIcon() {
|
||||
AsyncImage(url: URL(string: icon.value)) { image in
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 32, height: 32)
|
||||
} placeholder: {
|
||||
Image(systemName: "app.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 32, height: 32)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(product.displayName)
|
||||
.font(.headline)
|
||||
Text("版本 \(version)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
Divider()
|
||||
|
||||
VStack(spacing: 15) {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
.frame(width: 40, height: 40)
|
||||
|
||||
Text("正在获取包信息...")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
if !loadingState.currentTask.isEmpty {
|
||||
Text(loadingState.currentTask)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Button("取消") {
|
||||
dismiss()
|
||||
}
|
||||
.buttonStyle(BeautifulButtonStyle(baseColor: Color.gray.opacity(0.2)))
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.frame(width: 500, height: 400)
|
||||
}
|
||||
}
|
||||
|
||||
private struct CustomPackageSelectorView: View {
|
||||
@State private var selectedPackages: Set<UUID> = []
|
||||
@State private var searchText = ""
|
||||
@State private var showCopiedAlert = false
|
||||
|
||||
let productId: String
|
||||
let version: String
|
||||
let packages: [Package]
|
||||
let dependenciesToDownload: [DependenciesToDownload]
|
||||
let onDownloadStart: ([DependenciesToDownload]) -> Void
|
||||
let onCancel: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Text("选择要下载的包")
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
|
||||
Button(action: copyAllInfo) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "doc.on.doc")
|
||||
.font(.system(size: 12))
|
||||
Text("复制全部")
|
||||
.font(.system(size: 12))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.buttonStyle(BeautifulButtonStyle(baseColor: Color.blue))
|
||||
.help("复制所有包信息")
|
||||
|
||||
Button("取消") {
|
||||
onCancel()
|
||||
}
|
||||
.buttonStyle(BeautifulButtonStyle(baseColor: Color.gray.opacity(0.2)))
|
||||
}
|
||||
.padding()
|
||||
|
||||
Divider()
|
||||
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 8) {
|
||||
ForEach(dependenciesToDownload, id: \.sapCode) { dependency in
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "cube.box.fill")
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(.blue.opacity(0.8))
|
||||
|
||||
Text("\(dependency.sapCode) \(dependency.version)\(dependency.sapCode != "APRO" ? " - (\(dependency.buildGuid))" : "")")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.foregroundColor(.primary.opacity(0.8))
|
||||
.textSelection(.enabled)
|
||||
|
||||
if dependency.sapCode != "APRO" {
|
||||
Button(action: {
|
||||
copyToClipboard(dependency.buildGuid)
|
||||
}) {
|
||||
Image(systemName: "doc.on.doc")
|
||||
.font(.system(size: 10))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.buttonStyle(BeautifulButtonStyle(baseColor: .blue))
|
||||
.help("复制 buildGuid")
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
|
||||
ForEach(dependency.packages) { package in
|
||||
EnhancedPackageRow(
|
||||
package: package,
|
||||
isSelected: selectedPackages.contains(package.id),
|
||||
onToggle: { isSelected in
|
||||
if !package.isRequired {
|
||||
if isSelected {
|
||||
selectedPackages.insert(package.id)
|
||||
} else {
|
||||
selectedPackages.remove(package.id)
|
||||
}
|
||||
}
|
||||
},
|
||||
onCopyPackageInfo: {
|
||||
copyPackageInfo(package)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
.background(Color(NSColor.controlBackgroundColor).opacity(0.3))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
HStack {
|
||||
Text("已选择 \(selectedPackages.count) 个包")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text("总大小: \(formattedTotalSize)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("开始下载") {
|
||||
startCustomDownload()
|
||||
}
|
||||
.buttonStyle(BeautifulButtonStyle(baseColor: Color.blue))
|
||||
.disabled(selectedPackages.isEmpty)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.frame(width: 800, height: 650)
|
||||
.onAppear {
|
||||
initializeSelection()
|
||||
}
|
||||
.popover(isPresented: $showCopiedAlert, arrowEdge: .trailing) {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
Text("已复制")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
|
||||
private var formattedTotalSize: String {
|
||||
let totalSize = selectedPackages.compactMap { id in
|
||||
packages.first { $0.id == id }?.downloadSize
|
||||
}.reduce(0, +)
|
||||
|
||||
return ByteCountFormatter.string(fromByteCount: totalSize, countStyle: .file)
|
||||
}
|
||||
|
||||
private func initializeSelection() {
|
||||
for package in packages {
|
||||
if package.isRequired || package.isSelected {
|
||||
selectedPackages.insert(package.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func startCustomDownload() {
|
||||
for dependency in dependenciesToDownload {
|
||||
for package in dependency.packages {
|
||||
package.isSelected = selectedPackages.contains(package.id)
|
||||
}
|
||||
}
|
||||
|
||||
let finalDependencies = dependenciesToDownload.filter { dependency in
|
||||
dependency.packages.contains { $0.isSelected }
|
||||
}
|
||||
|
||||
onDownloadStart(finalDependencies)
|
||||
}
|
||||
|
||||
private func copyToClipboard(_ text: String) {
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.clearContents()
|
||||
pasteboard.setString(text, forType: .string)
|
||||
showCopiedAlert = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
|
||||
showCopiedAlert = false
|
||||
}
|
||||
}
|
||||
|
||||
private func copyPackageInfo(_ package: Package) {
|
||||
let packageInfo = "\(package.fullPackageName) (\(package.packageVersion)) - \(package.type)"
|
||||
copyToClipboard(packageInfo)
|
||||
}
|
||||
|
||||
private func copyAllInfo() {
|
||||
var result = ""
|
||||
|
||||
for (index, dependency) in dependenciesToDownload.enumerated() {
|
||||
let dependencyInfo: String
|
||||
if dependency.sapCode == "APRO" {
|
||||
dependencyInfo = "\(dependency.sapCode) \(dependency.version)"
|
||||
} else {
|
||||
dependencyInfo = "\(dependency.sapCode) \(dependency.version) - (\(dependency.buildGuid))"
|
||||
}
|
||||
result += dependencyInfo + "\n"
|
||||
|
||||
for (pkgIndex, package) in dependency.packages.enumerated() {
|
||||
let isLastPackage = pkgIndex == dependency.packages.count - 1
|
||||
let prefix = isLastPackage ? " └── " : " ├── "
|
||||
result += "\(prefix)\(package.fullPackageName) (\(package.packageVersion)) - \(package.type)\n"
|
||||
}
|
||||
|
||||
if index < dependenciesToDownload.count - 1 {
|
||||
result += "\n"
|
||||
}
|
||||
}
|
||||
|
||||
copyToClipboard(result)
|
||||
}
|
||||
}
|
||||
|
||||
private struct EnhancedPackageRow: View {
|
||||
let package: Package
|
||||
let isSelected: Bool
|
||||
let onToggle: (Bool) -> Void
|
||||
let onCopyPackageInfo: () -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 8) {
|
||||
Button(action: {
|
||||
if !package.isRequired {
|
||||
onToggle(!isSelected)
|
||||
}
|
||||
}) {
|
||||
Image(systemName: isSelected ? "checkmark.square.fill" : "square")
|
||||
.foregroundColor(package.isRequired ? .secondary : .blue)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.disabled(package.isRequired)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 6) {
|
||||
Text("\(package.fullPackageName) (\(package.packageVersion))")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundColor(.primary.opacity(0.8))
|
||||
.textSelection(.enabled)
|
||||
|
||||
Text(package.type)
|
||||
.font(.system(size: 10, weight: .medium))
|
||||
.padding(.horizontal, 5)
|
||||
.padding(.vertical, 2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(package.type == "core" ? Color.blue.opacity(0.1) : Color.orange.opacity(0.1))
|
||||
)
|
||||
.foregroundColor(package.type == "core" ? .blue : .orange)
|
||||
|
||||
if package.isRequired {
|
||||
Text("必需")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.red.opacity(0.8))
|
||||
.cornerRadius(4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(package.formattedSize)
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Button(action: onCopyPackageInfo) {
|
||||
Image(systemName: "doc.on.doc")
|
||||
.font(.system(size: 9))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.buttonStyle(BeautifulButtonStyle(baseColor: .gray.opacity(0.6)))
|
||||
.help("复制包信息")
|
||||
}
|
||||
|
||||
if !package.condition.isEmpty {
|
||||
Text("条件: \(package.condition)")
|
||||
.font(.system(size: 10))
|
||||
.foregroundColor(.secondary.opacity(0.8))
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 6)
|
||||
.background(isSelected ? Color.blue.opacity(0.1) : Color.clear)
|
||||
.overlay(
|
||||
Rectangle()
|
||||
.stroke(Color.secondary.opacity(0.2), lineWidth: 0.5)
|
||||
.opacity(0.5),
|
||||
alignment: .bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ struct VersionPickerView: View {
|
||||
@StorageValue(\.defaultLanguage) private var defaultLanguage
|
||||
@StorageValue(\.downloadAppleSilicon) private var downloadAppleSilicon
|
||||
@State private var expandedVersions: Set<String> = []
|
||||
@State private var showingCustomDownload = false
|
||||
@State private var customDownloadVersion = ""
|
||||
|
||||
private let productId: String
|
||||
private let onSelect: (String) -> Void
|
||||
@@ -41,10 +43,86 @@ struct VersionPickerView: View {
|
||||
productId: productId,
|
||||
expandedVersions: $expandedVersions,
|
||||
onSelect: onSelect,
|
||||
dismiss: dismiss
|
||||
dismiss: dismiss,
|
||||
onCustomDownload: { version in
|
||||
customDownloadVersion = version
|
||||
showingCustomDownload = true
|
||||
}
|
||||
)
|
||||
}
|
||||
.frame(width: VersionPickerConstants.viewWidth, height: VersionPickerConstants.viewHeight)
|
||||
.sheet(isPresented: $showingCustomDownload) {
|
||||
CustomDownloadView(
|
||||
productId: productId,
|
||||
version: customDownloadVersion,
|
||||
onDownloadStart: { dependencies in
|
||||
handleCustomDownload(dependencies: dependencies)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func getDestinationURL(productId: String, version: String, language: String) async throws -> URL {
|
||||
let platform = globalProducts.first(where: { $0.id == productId && $0.version == version })?.platforms.first?.id ?? "unknown"
|
||||
let installerName = productId == "APRO"
|
||||
? "Adobe Downloader \(productId)_\(version)_\(platform).dmg"
|
||||
: "Adobe Downloader \(productId)_\(version)-\(language)-\(platform)"
|
||||
|
||||
if StorageData.shared.useDefaultDirectory && !StorageData.shared.defaultDirectory.isEmpty {
|
||||
return URL(fileURLWithPath: StorageData.shared.defaultDirectory)
|
||||
.appendingPathComponent(installerName)
|
||||
}
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
DispatchQueue.main.async {
|
||||
let panel = NSOpenPanel()
|
||||
panel.title = "选择保存位置"
|
||||
panel.canCreateDirectories = true
|
||||
panel.canChooseDirectories = true
|
||||
panel.canChooseFiles = false
|
||||
panel.directoryURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
|
||||
|
||||
if panel.runModal() == .OK, let selectedURL = panel.url {
|
||||
continuation.resume(returning: selectedURL.appendingPathComponent(installerName))
|
||||
} else {
|
||||
continuation.resume(throwing: NetworkError.cancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleCustomDownload(dependencies: [DependenciesToDownload]) {
|
||||
showingCustomDownload = false
|
||||
|
||||
Task {
|
||||
let destinationURL: URL
|
||||
do {
|
||||
destinationURL = try await getDestinationURL(
|
||||
productId: productId,
|
||||
version: customDownloadVersion,
|
||||
language: StorageData.shared.defaultLanguage
|
||||
)
|
||||
} catch {
|
||||
await MainActor.run { dismiss() }
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try await globalNetworkManager.startCustomDownload(
|
||||
productId: productId,
|
||||
selectedVersion: customDownloadVersion,
|
||||
language: StorageData.shared.defaultLanguage,
|
||||
destinationURL: destinationURL,
|
||||
customDependencies: dependencies
|
||||
)
|
||||
} catch {
|
||||
print("自定义下载失败: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +193,7 @@ private struct VersionListView: View {
|
||||
@Binding var expandedVersions: Set<String>
|
||||
let onSelect: (String) -> Void
|
||||
let dismiss: DismissAction
|
||||
let onCustomDownload: (String) -> Void
|
||||
@State private var scrollPosition: String?
|
||||
@State private var cachedVersions: [(key: String, value: Product.Platform)] = []
|
||||
|
||||
@@ -130,7 +209,8 @@ private struct VersionListView: View {
|
||||
info: info,
|
||||
isExpanded: expandedVersions.contains(version),
|
||||
onSelect: handleVersionSelect,
|
||||
onToggle: handleVersionToggle
|
||||
onToggle: handleVersionToggle,
|
||||
onCustomDownload: handleCustomDownload
|
||||
)
|
||||
.id(version)
|
||||
.transition(.opacity)
|
||||
@@ -210,6 +290,10 @@ private struct VersionListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleCustomDownload(_ version: String) {
|
||||
onCustomDownload(version)
|
||||
}
|
||||
}
|
||||
|
||||
private struct VersionRow: View, Equatable {
|
||||
@@ -221,6 +305,7 @@ private struct VersionRow: View, Equatable {
|
||||
let isExpanded: Bool
|
||||
let onSelect: (String) -> Void
|
||||
let onToggle: (String) -> Void
|
||||
let onCustomDownload: (String) -> Void
|
||||
|
||||
static func == (lhs: VersionRow, rhs: VersionRow) -> Bool {
|
||||
lhs.productId == rhs.productId &&
|
||||
@@ -249,7 +334,8 @@ private struct VersionRow: View, Equatable {
|
||||
VersionDetails(
|
||||
info: info,
|
||||
version: version,
|
||||
onSelect: onSelect
|
||||
onSelect: onSelect,
|
||||
onCustomDownload: onCustomDownload
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -393,6 +479,7 @@ private struct VersionDetails: View {
|
||||
let info: Product.Platform
|
||||
let version: String
|
||||
let onSelect: (String) -> Void
|
||||
let onCustomDownload: (String) -> Void
|
||||
|
||||
private var hasDependencies: Bool {
|
||||
!(info.languageSet.first?.dependencies.isEmpty ?? true)
|
||||
@@ -462,7 +549,13 @@ private struct VersionDetails: View {
|
||||
.cornerRadius(6)
|
||||
}
|
||||
|
||||
DownloadButton(version: version, onSelect: onSelect)
|
||||
DownloadButton(
|
||||
version: version,
|
||||
onSelect: onSelect,
|
||||
onCustomDownload: { version in
|
||||
onCustomDownload(version)
|
||||
}
|
||||
)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.bottom, 8)
|
||||
@@ -628,10 +721,11 @@ private struct ModulesList: View {
|
||||
private struct DownloadButton: View {
|
||||
let version: String
|
||||
let onSelect: (String) -> Void
|
||||
let onCustomDownload: (String) -> Void
|
||||
|
||||
var body: some View {
|
||||
Button("下载") {
|
||||
onSelect(version)
|
||||
onCustomDownload(version)
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.buttonStyle(BeautifulButtonStyle(baseColor: Color.blue))
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
},
|
||||
"(可能导致处理失败)" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -27,6 +28,7 @@
|
||||
}
|
||||
},
|
||||
"(无法使用安装功能)" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -135,6 +137,12 @@
|
||||
},
|
||||
"•" : {
|
||||
|
||||
},
|
||||
"✅" : {
|
||||
|
||||
},
|
||||
"❌" : {
|
||||
|
||||
},
|
||||
"Adobe Creative Cloud" : {
|
||||
|
||||
@@ -278,7 +286,6 @@
|
||||
|
||||
},
|
||||
"Debug 模式" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -359,6 +366,7 @@
|
||||
}
|
||||
},
|
||||
"Helper 未连接" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -414,9 +422,15 @@
|
||||
},
|
||||
"macOS %@" : {
|
||||
|
||||
},
|
||||
"Match:" : {
|
||||
|
||||
},
|
||||
"OK" : {
|
||||
|
||||
},
|
||||
"Reason:" : {
|
||||
|
||||
},
|
||||
"Setup 组件是 Adobe 官方的安装程序组件,我们需要对其进行修改以实现绕过验证的功能。如果没有下载并处理 Setup 组件,将无法使用安装功能。" : {
|
||||
"localizations" : {
|
||||
@@ -439,6 +453,7 @@
|
||||
}
|
||||
},
|
||||
"Setup 组件未处理,无法安装" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -457,6 +472,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Target:" : {
|
||||
|
||||
},
|
||||
"v%@" : {
|
||||
|
||||
@@ -1076,7 +1094,6 @@
|
||||
}
|
||||
},
|
||||
"可选模块" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1175,6 +1192,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"复制全部" : {
|
||||
|
||||
},
|
||||
"复制包信息" : {
|
||||
|
||||
},
|
||||
"复制命令" : {
|
||||
"localizations" : {
|
||||
@@ -1205,6 +1228,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"复制所有包信息" : {
|
||||
|
||||
},
|
||||
"多次尝试连接失败" : {
|
||||
"localizations" : {
|
||||
@@ -1441,7 +1467,6 @@
|
||||
}
|
||||
},
|
||||
"展开全部" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1482,6 +1507,7 @@
|
||||
}
|
||||
},
|
||||
"已处理" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1492,6 +1518,7 @@
|
||||
}
|
||||
},
|
||||
"已备份" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1593,6 +1620,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"已选择 %lld 个包" : {
|
||||
|
||||
},
|
||||
"常见问题" : {
|
||||
"localizations" : {
|
||||
@@ -1625,6 +1655,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"开始下载" : {
|
||||
|
||||
},
|
||||
"开始清理" : {
|
||||
"localizations" : {
|
||||
@@ -1665,6 +1698,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"必需" : {
|
||||
|
||||
},
|
||||
"总大小: %@" : {
|
||||
|
||||
},
|
||||
"所有操作已成功完成" : {
|
||||
"localizations" : {
|
||||
@@ -1727,7 +1766,6 @@
|
||||
}
|
||||
},
|
||||
"折叠全部" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1738,7 +1776,6 @@
|
||||
}
|
||||
},
|
||||
"持久化文件" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -2101,6 +2138,7 @@
|
||||
}
|
||||
},
|
||||
"未处理" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -2111,6 +2149,7 @@
|
||||
}
|
||||
},
|
||||
"未备份" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -2131,7 +2170,6 @@
|
||||
}
|
||||
},
|
||||
"未对 Setup 组件进行处理或者 Setup 组件不存在,无法使用安装功能\n你可以通过设置页面再次对 Setup 组件进行处理" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -2142,6 +2180,7 @@
|
||||
}
|
||||
},
|
||||
"未对 Setup 组件进行处理或者 Setup 组件不存在,无法使用安装功能\n你可以通过设置页面对 Setup 组件进行处理" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -2227,6 +2266,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"条件: %@" : {
|
||||
|
||||
},
|
||||
"查看" : {
|
||||
"localizations" : {
|
||||
@@ -2308,6 +2350,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"正在准备自定义下载..." : {
|
||||
|
||||
},
|
||||
"正在加载..." : {
|
||||
"localizations" : {
|
||||
@@ -2498,6 +2543,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"正在获取包信息..." : {
|
||||
|
||||
},
|
||||
"正在连接" : {
|
||||
"localizations" : {
|
||||
@@ -2609,6 +2657,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"版本 %@" : {
|
||||
|
||||
},
|
||||
"版本不兼容: 当前版本 %@, 需要版本 %@" : {
|
||||
"comment" : "Incompatible version",
|
||||
@@ -3199,6 +3250,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"选择要下载的包" : {
|
||||
|
||||
},
|
||||
"选择要清理的内容" : {
|
||||
"localizations" : {
|
||||
|
||||
Reference in New Issue
Block a user