diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 788f9af6..7e034a72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 1d0aeb9a..3f73c764 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -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,20 +1553,20 @@ minimumVersion = 4.3.1; }; }; - E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = { + E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/apple/swift-collections.git"; + repositoryURL = "https://github.com/sparkle-project/Sparkle"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.0.5; + minimumVersion = 2.6.4; }; }; - E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */ = { + E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/sparkle-project/Sparkle"; + repositoryURL = "https://github.com/apple/swift-collections.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.5.2; + minimumVersion = 1.0.5; }; }; E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */ = { @@ -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; diff --git a/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 884e347e..6acf19b7 100644 --- a/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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" } }, { diff --git a/Xcodes/Backend/AppState+Runtimes.swift b/Xcodes/Backend/AppState+Runtimes.swift index 54be1114..ca8f5e46 100644 --- a/Xcodes/Backend/AppState+Runtimes.swift +++ b/Xcodes/Backend/AppState+Runtimes.swift @@ -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) diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 48d41a55..11906294 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -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, diff --git a/Xcodes/Backend/Environment.swift b/Xcodes/Backend/Environment.swift index e75a8750..61574ce5 100644 --- a/Xcodes/Backend/Environment.swift +++ b/Xcodes/Backend/Environment.swift @@ -196,6 +196,77 @@ public struct Shell { return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"]) } + public var downloadRuntime: (String, String) -> AsyncThrowingStream = { platform, version in + return AsyncThrowingStream { 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 { diff --git a/Xcodes/Backend/Progress+.swift b/Xcodes/Backend/Progress+.swift index 6e7688c9..ce2ff3be 100644 --- a/Xcodes/Backend/Progress+.swift +++ b/Xcodes/Backend/Progress+.swift @@ -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") + } + + } } diff --git a/Xcodes/Backend/XcodeCommands.swift b/Xcodes/Backend/XcodeCommands.swift index b168bd4d..e33d1be3 100644 --- a/Xcodes/Backend/XcodeCommands.swift +++ b/Xcodes/Backend/XcodeCommands.swift @@ -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) } diff --git a/Xcodes/Frontend/Common/ObservingProgressIndicator.swift b/Xcodes/Frontend/Common/ObservingProgressIndicator.swift index a6774866..3302f0a3 100644 --- a/Xcodes/Frontend/Common/ObservingProgressIndicator.swift +++ b/Xcodes/Frontend/Common/ObservingProgressIndicator.swift @@ -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) } diff --git a/Xcodes/Frontend/Common/ProgressIndicator.swift b/Xcodes/Frontend/Common/ProgressIndicator.swift index 4c11f0a6..bb801869 100644 --- a/Xcodes/Frontend/Common/ProgressIndicator.swift +++ b/Xcodes/Frontend/Common/ProgressIndicator.swift @@ -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) } } diff --git a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift index 75339ab4..e4bb64ff 100644 --- a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift +++ b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift @@ -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) } diff --git a/Xcodes/Frontend/InfoPane/RuntimeInstallationStepDetailView.swift b/Xcodes/Frontend/InfoPane/RuntimeInstallationStepDetailView.swift index f59f0417..251c6bdb 100644 --- a/Xcodes/Frontend/InfoPane/RuntimeInstallationStepDetailView.swift +++ b/Xcodes/Frontend/InfoPane/RuntimeInstallationStepDetailView.swift @@ -26,8 +26,12 @@ struct RuntimeInstallationStepDetailView: View { ) case .installing, .trashingArchive: - ProgressView() - .scaleEffect(0.5) + ObservingProgressIndicator( + Progress(), + controlSize: .regular, + style: .bar, + showsAdditionalDescription: false + ) } } } diff --git a/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift b/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift index 3bf7db56..1d605fa0 100644 --- a/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift +++ b/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift @@ -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) } diff --git a/Xcodes/Resources/Localizable.xcstrings b/Xcodes/Resources/Localizable.xcstrings index de92c833..b1cd3b96 100644 --- a/Xcodes/Resources/Localizable.xcstrings +++ b/Xcodes/Resources/Localizable.xcstrings @@ -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" : { diff --git a/Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeInstallationStep.swift b/Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeInstallationStep.swift index 8d5513d3..9a4349e1 100644 --- a/Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeInstallationStep.swift +++ b/Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeInstallationStep.swift @@ -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 {