mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
Merge branch 'main' into security-key-auth
# Conflicts: # Xcodes.xcodeproj/project.pbxproj
This commit is contained in:
commit
f4567bdf1e
15 changed files with 361 additions and 41 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -8,10 +8,10 @@ on:
|
|||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run tests
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.0.1.app
|
||||
DEVELOPER_DIR: /Applications/Xcode_16.app
|
||||
run: xcodebuild test -scheme Xcodes
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@
|
|||
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */ = {isa = PBXBuildFile; productRef = E689540225BE8C64000EBCEA /* DockProgress */; };
|
||||
E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; };
|
||||
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; };
|
||||
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E83FDC432CBB649100679C6B /* Sparkle */; };
|
||||
E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */; };
|
||||
E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */; };
|
||||
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F532B333864003F3959 /* PlatformsListView.swift */; };
|
||||
|
|
@ -124,7 +125,6 @@
|
|||
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86671262B309D2F0048559A /* PlatformsView.swift */; };
|
||||
E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; };
|
||||
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; };
|
||||
E891A1C42B43ACF900A1B9D1 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E891A1C32B43ACF900A1B9D1 /* Sparkle */; };
|
||||
E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89342F925EDCC17007CF557 /* NotificationManager.swift */; };
|
||||
E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; };
|
||||
E8B20CBF2A2EDEC20057D816 /* SDKs+Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B20CBE2A2EDEC20057D816 /* SDKs+Xcode.swift */; };
|
||||
|
|
@ -357,7 +357,7 @@
|
|||
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */,
|
||||
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */,
|
||||
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */,
|
||||
E891A1C42B43ACF900A1B9D1 /* Sparkle in Frameworks */,
|
||||
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */,
|
||||
CAA858CD25A3D8BC00ACF8C0 /* ErrorHandling in Frameworks */,
|
||||
E8C0EB1A291EF43E0081528A /* XcodesKit in Frameworks */,
|
||||
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */,
|
||||
|
|
@ -721,7 +721,7 @@
|
|||
E8C0EB19291EF43E0081528A /* XcodesKit */,
|
||||
E8F44A1D296B4CD7002D6592 /* Path */,
|
||||
E84E4F562B335094003F3959 /* OrderedCollections */,
|
||||
E891A1C32B43ACF900A1B9D1 /* Sparkle */,
|
||||
E83FDC432CBB649100679C6B /* Sparkle */,
|
||||
334A932B2CA885A400A5E079 /* LibFido2Swift */,
|
||||
);
|
||||
productName = XcodesMac;
|
||||
|
|
@ -810,7 +810,7 @@
|
|||
E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */,
|
||||
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */,
|
||||
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
|
||||
E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */,
|
||||
E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */,
|
||||
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */,
|
||||
);
|
||||
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
|
||||
|
|
@ -1091,7 +1091,7 @@
|
|||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
|
|
@ -1103,7 +1103,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 2.1.2;
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
|
||||
PRODUCT_NAME = Xcodes;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1343,7 +1343,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = ZU6GR6B2FY;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
|
|
@ -1355,7 +1355,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 2.1.2;
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
|
||||
PRODUCT_NAME = Xcodes;
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -1371,7 +1371,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = ZU6GR6B2FY;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
|
|
@ -1383,7 +1383,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 2.1.2;
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
|
||||
PRODUCT_NAME = Xcodes;
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -1553,6 +1553,14 @@
|
|||
minimumVersion = 4.3.1;
|
||||
};
|
||||
};
|
||||
E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/sparkle-project/Sparkle";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 2.6.4;
|
||||
};
|
||||
};
|
||||
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/apple/swift-collections.git";
|
||||
|
|
@ -1561,14 +1569,6 @@
|
|||
minimumVersion = 1.0.5;
|
||||
};
|
||||
};
|
||||
E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/sparkle-project/Sparkle";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 2.5.2;
|
||||
};
|
||||
};
|
||||
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mxcl/Path.swift";
|
||||
|
|
@ -1636,16 +1636,16 @@
|
|||
package = E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */;
|
||||
productName = DockProgress;
|
||||
};
|
||||
E83FDC432CBB649100679C6B /* Sparkle */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */;
|
||||
productName = Sparkle;
|
||||
};
|
||||
E84E4F562B335094003F3959 /* OrderedCollections */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */;
|
||||
productName = OrderedCollections;
|
||||
};
|
||||
E891A1C32B43ACF900A1B9D1 /* Sparkle */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */;
|
||||
productName = Sparkle;
|
||||
};
|
||||
E8C0EB19291EF43E0081528A /* XcodesKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = XcodesKit;
|
||||
|
|
|
|||
|
|
@ -87,8 +87,8 @@
|
|||
"repositoryURL": "https://github.com/sparkle-project/Sparkle/",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "47d3d90aee3c52b6f61d04ceae426e607df62347",
|
||||
"version": "2.5.2"
|
||||
"revision": "0ef1ee0220239b3776f433314515fd849025673f",
|
||||
"version": "2.6.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import OSLog
|
|||
import Combine
|
||||
import Path
|
||||
import AppleAPI
|
||||
import Version
|
||||
|
||||
extension AppState {
|
||||
func updateDownloadableRuntimes() {
|
||||
|
|
@ -48,6 +49,69 @@ extension AppState {
|
|||
}
|
||||
|
||||
func downloadRuntime(runtime: DownloadableRuntime) {
|
||||
guard let selectedXcode = self.allXcodes.first(where: { $0.selected }) else {
|
||||
Logger.appState.error("No selected Xcode")
|
||||
DispatchQueue.main.async {
|
||||
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: "No selected Xcode. Please make an Xcode active")
|
||||
}
|
||||
return
|
||||
}
|
||||
// new runtimes
|
||||
if runtime.contentType == .cryptexDiskImage {
|
||||
// only selected xcodes > 16.1 beta 3 can download runtimes via a xcodebuild -downloadPlatform version
|
||||
// only Runtimes coming from cryptexDiskImage can be downloaded via xcodebuild
|
||||
if selectedXcode.version > Version(major: 16, minor: 0, patch: 0) {
|
||||
downloadRuntimeViaXcodeBuild(runtime: runtime)
|
||||
} else {
|
||||
// not supported
|
||||
Logger.appState.error("Trying to download a runtime we can't download")
|
||||
DispatchQueue.main.async {
|
||||
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: "Sorry. Apple only supports downloading runtimes iOS 18+, tvOS 18+, watchOS 11+, visionOS 2+ with Xcode 16.1+. Please download and make active.")
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
downloadRuntimeObseleteWay(runtime: runtime)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadRuntimeViaXcodeBuild(runtime: DownloadableRuntime) {
|
||||
runtimePublishers[runtime.identifier] = Task {
|
||||
do {
|
||||
for try await progress in Current.shell.downloadRuntime(runtime.platform.shortName, runtime.simulatorVersion.buildUpdate) {
|
||||
if progress.isIndeterminate {
|
||||
DispatchQueue.main.async {
|
||||
self.setInstallationStep(of: runtime, to: .installing, postNotification: false)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.setInstallationStep(of: runtime, to: .downloading(progress: progress), postNotification: false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Logger.appState.debug("Done downloading runtime - \(runtime.name)")
|
||||
DispatchQueue.main.async {
|
||||
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
|
||||
self.downloadableRuntimes[index].installState = .installed
|
||||
self.update()
|
||||
}
|
||||
|
||||
} catch {
|
||||
Logger.appState.error("Error downloading runtime: \(error.localizedDescription)")
|
||||
DispatchQueue.main.async {
|
||||
self.error = error
|
||||
if let error = error as? String {
|
||||
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error)
|
||||
} else {
|
||||
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloadRuntimeObseleteWay(runtime: DownloadableRuntime) {
|
||||
runtimePublishers[runtime.identifier] = Task {
|
||||
do {
|
||||
let downloadedURL = try await downloadRunTimeFull(runtime: runtime)
|
||||
|
|
|
|||
|
|
@ -498,6 +498,11 @@ class AppState: ObservableObject {
|
|||
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
|
||||
|
||||
installationPublishers[id] = signInIfNeeded()
|
||||
.handleEvents(
|
||||
receiveSubscription: { [unowned self] _ in
|
||||
self.setInstallationStep(of: availableXcode.version, to: .authenticating)
|
||||
}
|
||||
)
|
||||
.flatMap { [unowned self] in
|
||||
// signInIfNeeded might finish before the user actually authenticates if UI is involved.
|
||||
// This publisher will wait for the @Published authentication state to change to authenticated or unauthenticated before finishing,
|
||||
|
|
|
|||
|
|
@ -196,6 +196,77 @@ public struct Shell {
|
|||
return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"])
|
||||
}
|
||||
|
||||
public var downloadRuntime: (String, String) -> AsyncThrowingStream<Progress, Error> = { platform, version in
|
||||
return AsyncThrowingStream<Progress, Error> { continuation in
|
||||
Task {
|
||||
// Assume progress will not have data races, so we manually opt-out isolation checks.
|
||||
nonisolated(unsafe) var progress = Progress()
|
||||
progress.kind = .file
|
||||
progress.fileOperationKind = .downloading
|
||||
|
||||
let process = Process()
|
||||
let xcodeBuildPath = Path.root.usr.bin.join("xcodebuild").url
|
||||
|
||||
process.executableURL = xcodeBuildPath
|
||||
process.arguments = [
|
||||
"-downloadPlatform",
|
||||
"\(platform)",
|
||||
"-buildVersion",
|
||||
"\(version)"
|
||||
]
|
||||
|
||||
let stdOutPipe = Pipe()
|
||||
process.standardOutput = stdOutPipe
|
||||
let stdErrPipe = Pipe()
|
||||
process.standardError = stdErrPipe
|
||||
|
||||
let observer = NotificationCenter.default.addObserver(
|
||||
forName: .NSFileHandleDataAvailable,
|
||||
object: nil,
|
||||
queue: OperationQueue.main
|
||||
) { note in
|
||||
guard
|
||||
// This should always be the case for Notification.Name.NSFileHandleDataAvailable
|
||||
let handle = note.object as? FileHandle,
|
||||
handle === stdOutPipe.fileHandleForReading || handle === stdErrPipe.fileHandleForReading
|
||||
else { return }
|
||||
|
||||
defer { handle.waitForDataInBackgroundAndNotify() }
|
||||
|
||||
let string = String(decoding: handle.availableData, as: UTF8.self)
|
||||
|
||||
// TODO: fix warning. ObservingProgressView is currently tied to an updating progress
|
||||
progress.updateFromXcodebuild(text: string)
|
||||
|
||||
continuation.yield(progress)
|
||||
}
|
||||
|
||||
stdOutPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
stdErrPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
|
||||
continuation.onTermination = { @Sendable _ in
|
||||
process.terminate()
|
||||
NotificationCenter.default.removeObserver(observer, name: .NSFileHandleDataAvailable, object: nil)
|
||||
}
|
||||
|
||||
do {
|
||||
try process.run()
|
||||
} catch {
|
||||
continuation.finish(throwing: error)
|
||||
}
|
||||
|
||||
process.waitUntilExit()
|
||||
|
||||
NotificationCenter.default.removeObserver(observer, name: .NSFileHandleDataAvailable, object: nil)
|
||||
|
||||
guard process.terminationReason == .exit, process.terminationStatus == 0 else {
|
||||
continuation.finish(throwing: ProcessExecutionError(process: process, standardOutput: "", standardError: ""))
|
||||
return
|
||||
}
|
||||
continuation.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Files {
|
||||
|
|
|
|||
|
|
@ -70,5 +70,38 @@ extension Progress {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func updateFromXcodebuild(text: String) {
|
||||
self.totalUnitCount = 100
|
||||
self.completedUnitCount = 0
|
||||
self.localizedAdditionalDescription = "" // to not show the addtional
|
||||
|
||||
do {
|
||||
|
||||
let downloadPattern = #"(\d+\.\d+)% \(([\d.]+ (?:MB|GB)) of ([\d.]+ GB)\)"#
|
||||
let downloadRegex = try NSRegularExpression(pattern: downloadPattern)
|
||||
|
||||
// Search for matches in the text
|
||||
if let match = downloadRegex.firstMatch(in: text, range: NSRange(text.startIndex..., in: text)) {
|
||||
// Extract the percentage - simpler then trying to extract size MB/GB and convert to bytes.
|
||||
if let percentRange = Range(match.range(at: 1), in: text), let percentDouble = Double(text[percentRange]) {
|
||||
let percent = Int64(percentDouble.rounded())
|
||||
self.completedUnitCount = percent
|
||||
}
|
||||
}
|
||||
|
||||
// "Downloading tvOS 18.1 Simulator (22J5567a): Installing..." or
|
||||
// "Downloading tvOS 18.1 Simulator (22J5567a): Installing (registering download)..."
|
||||
if text.range(of: "Installing") != nil {
|
||||
// sets the progress to indeterminite to show animating progress
|
||||
self.totalUnitCount = 0
|
||||
self.completedUnitCount = 0
|
||||
}
|
||||
|
||||
} catch {
|
||||
Logger.appState.error("Invalid regular expression")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,12 +35,11 @@ struct XcodeCommands: Commands {
|
|||
|
||||
struct InstallButton: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@State private var isLoading = false
|
||||
|
||||
let xcode: Xcode?
|
||||
|
||||
var body: some View {
|
||||
ProgressButton(isInProgress: isLoading) {
|
||||
Button {
|
||||
install()
|
||||
} label: {
|
||||
Text("Install")
|
||||
|
|
@ -49,7 +48,6 @@ struct InstallButton: View {
|
|||
}
|
||||
|
||||
private func install() {
|
||||
isLoading = true
|
||||
guard let xcode = xcode else { return }
|
||||
appState.checkMinVersionAndInstall(id: xcode.id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public struct ObservingProgressIndicator: View {
|
|||
self.progress = progress
|
||||
cancellable = progress.publisher(for: \.fractionCompleted)
|
||||
.combineLatest(progress.publisher(for: \.localizedAdditionalDescription))
|
||||
.combineLatest(progress.publisher(for: \.isIndeterminate))
|
||||
.throttle(for: 1.0, scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { [weak self] _ in self?.objectWillChange.send() }
|
||||
}
|
||||
|
|
@ -82,6 +83,18 @@ struct ObservingProgressBar_Previews: PreviewProvider {
|
|||
style: .bar,
|
||||
showsAdditionalDescription: true
|
||||
)
|
||||
|
||||
ObservingProgressIndicator(
|
||||
configure(Progress()) {
|
||||
$0.kind = .file
|
||||
$0.fileOperationKind = .downloading
|
||||
$0.totalUnitCount = 0
|
||||
$0.completedUnitCount = 0
|
||||
},
|
||||
controlSize: .regular,
|
||||
style: .bar,
|
||||
showsAdditionalDescription: true
|
||||
)
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@ struct ProgressIndicator: NSViewRepresentable {
|
|||
nsView.doubleValue = doubleValue
|
||||
nsView.controlSize = controlSize
|
||||
nsView.isIndeterminate = isIndeterminate
|
||||
nsView.usesThreadedAnimation = true
|
||||
|
||||
nsView.style = style
|
||||
nsView.startAnimation(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ struct InstallationStepDetailView: View {
|
|||
showsAdditionalDescription: true
|
||||
)
|
||||
|
||||
case .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
|
||||
case .authenticating, .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
|
||||
ProgressView()
|
||||
.scaleEffect(0.5)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,12 @@ struct RuntimeInstallationStepDetailView: View {
|
|||
)
|
||||
|
||||
case .installing, .trashingArchive:
|
||||
ProgressView()
|
||||
.scaleEffect(0.5)
|
||||
ObservingProgressIndicator(
|
||||
Progress(),
|
||||
controlSize: .regular,
|
||||
style: .bar,
|
||||
showsAdditionalDescription: false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ struct InstallationStepRowView: View {
|
|||
controlSize: .small,
|
||||
style: .spinning
|
||||
)
|
||||
case .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
|
||||
case .authenticating, .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
|
||||
ProgressView()
|
||||
.scaleEffect(0.5)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4446,6 +4446,131 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Authenticating" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"ca" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"el" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "認証中"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Authenticating"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AutomaticallyCreateSymbolicLink" : {
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import Foundation
|
|||
|
||||
// A numbered step
|
||||
public enum XcodeInstallationStep: Equatable, CustomStringConvertible {
|
||||
case authenticating
|
||||
case downloading(progress: Progress)
|
||||
case unarchiving
|
||||
case moving(destination: String)
|
||||
|
|
@ -22,6 +23,8 @@ public enum XcodeInstallationStep: Equatable, CustomStringConvertible {
|
|||
|
||||
public var message: String {
|
||||
switch self {
|
||||
case .authenticating:
|
||||
return localizeString("Authenticating")
|
||||
case .downloading:
|
||||
return localizeString("Downloading")
|
||||
case .unarchiving:
|
||||
|
|
@ -39,16 +42,17 @@ public enum XcodeInstallationStep: Equatable, CustomStringConvertible {
|
|||
|
||||
public var stepNumber: Int {
|
||||
switch self {
|
||||
case .downloading: return 1
|
||||
case .unarchiving: return 2
|
||||
case .moving: return 3
|
||||
case .trashingArchive: return 4
|
||||
case .checkingSecurity: return 5
|
||||
case .finishing: return 6
|
||||
case .authenticating: return 1
|
||||
case .downloading: return 2
|
||||
case .unarchiving: return 3
|
||||
case .moving: return 4
|
||||
case .trashingArchive: return 5
|
||||
case .checkingSecurity: return 6
|
||||
case .finishing: return 7
|
||||
}
|
||||
}
|
||||
|
||||
public var stepCount: Int { 6 }
|
||||
public var stepCount: Int { 7 }
|
||||
}
|
||||
|
||||
func localizeString(_ key: String, comment: String = "") -> String {
|
||||
|
|
|
|||
Loading…
Reference in a new issue