mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
Merge pull request #107 from RobotsAndPencils/matt/downloadStats
Display installation steps with download stats in info pane
This commit is contained in:
commit
69f5b707fa
8 changed files with 250 additions and 82 deletions
|
|
@ -75,7 +75,6 @@
|
|||
CABFAA432593104F00380FEE /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA422593104F00380FEE /* AboutView.swift */; };
|
||||
CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */; };
|
||||
CAC28188259EE27200B8AB0B /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = CAC28187259EE27200B8AB0B /* CombineExpectations */; };
|
||||
CAC281C8259F97E100B8AB0B /* InstallationStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281C7259F97E100B8AB0B /* InstallationStepView.swift */; };
|
||||
CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */; };
|
||||
CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281D9259F985100B8AB0B /* InstallationStep.swift */; };
|
||||
CAC281E2259FA44600B8AB0B /* Bundle+XcodesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */; };
|
||||
|
|
@ -99,7 +98,10 @@
|
|||
CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4AB325B7D3AF0064FE51 /* AdvancedPreferencePane.swift */; };
|
||||
CAFE4ABC25B7D54B0064FE51 /* UpdatesPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */; };
|
||||
CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */; };
|
||||
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; };
|
||||
E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; };
|
||||
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; };
|
||||
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -227,7 +229,6 @@
|
|||
CABFAA2B2592FBFC00380FEE /* Configure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Configure.swift; path = Xcodes/Backend/Configure.swift; sourceTree = SOURCE_ROOT; };
|
||||
CABFAA422593104F00380FEE /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+InfoPlistValues.swift"; sourceTree = "<group>"; };
|
||||
CAC281C7259F97E100B8AB0B /* InstallationStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepView.swift; sourceTree = "<group>"; };
|
||||
CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingProgressIndicator.swift; sourceTree = "<group>"; };
|
||||
CAC281D9259F985100B8AB0B /* InstallationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStep.swift; sourceTree = "<group>"; };
|
||||
CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+XcodesTests.swift"; sourceTree = "<group>"; };
|
||||
|
|
@ -247,7 +248,7 @@
|
|||
CAE42486259A68A300B8B246 /* XcodeListCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListCategory.swift; sourceTree = "<group>"; };
|
||||
CAE4248B259A68B800B8B246 /* Optional+IsNotNil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+IsNotNil.swift"; sourceTree = "<group>"; };
|
||||
CAE424B3259A764700B8B246 /* AppState+Install.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppState+Install.swift"; sourceTree = "<group>"; };
|
||||
CAFBC3FF259AC17F00E2A3D8 /* InstallationStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepView.swift; sourceTree = "<group>"; };
|
||||
CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepRowView.swift; sourceTree = "<group>"; };
|
||||
CAFBC421259ACF8000E2A3D8 /* ObservingProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingProgressView.swift; sourceTree = "<group>"; };
|
||||
CAFBDB902598FE80003DCC5A /* SelectedXcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedXcode.swift; sourceTree = "<group>"; };
|
||||
CAFBDB942598FE96003DCC5A /* FocusedValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedValues.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -260,7 +261,9 @@
|
|||
CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatesPreferencePane.swift; sourceTree = "<group>"; };
|
||||
CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListViewRow.swift; sourceTree = "<group>"; };
|
||||
CAFFFEEE259CEAC400903F81 /* RingProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingProgressViewStyle.swift; sourceTree = "<group>"; };
|
||||
E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = "<group>"; };
|
||||
E8977EA225C11E1500835F80 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -387,14 +390,12 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */,
|
||||
CAFBDC67259A308B003DCC5A /* InfoPane.swift */,
|
||||
CAFBC3FF259AC17F00E2A3D8 /* InstallationStepView.swift */,
|
||||
CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */,
|
||||
CAFBDC4D2599B33D003DCC5A /* MainToolbar.swift */,
|
||||
CA44901E2463AD34003D8213 /* Tag.swift */,
|
||||
CAE42486259A68A300B8B246 /* XcodeListCategory.swift */,
|
||||
CAD2E7A32449574E00113D76 /* XcodeListView.swift */,
|
||||
CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */,
|
||||
CAC281C7259F97E100B8AB0B /* InstallationStepView.swift */,
|
||||
);
|
||||
path = XcodeList;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -437,6 +438,7 @@
|
|||
CA61A6DF259835580008926E /* Xcode.swift */,
|
||||
CA25192925A9644800F08414 /* XcodeInstallState.swift */,
|
||||
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */,
|
||||
E87DD6EA25D053FA00D86808 /* Progress+.swift */,
|
||||
);
|
||||
path = Backend;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -444,6 +446,7 @@
|
|||
CABFAA1A2592F7D900380FEE /* Frontend */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8E98A9425D863B100EC89A0 /* InfoPane */,
|
||||
63EAA4E9259944340046AB8F /* Common */,
|
||||
CA9FF8552595082000E47BAF /* About */,
|
||||
CAFE4AAA25B7D29B0064FE51 /* Preferences */,
|
||||
|
|
@ -547,6 +550,15 @@
|
|||
path = Preferences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E8E98A9425D863B100EC89A0 /* InfoPane */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CAFBDC67259A308B003DCC5A /* InfoPane.swift */,
|
||||
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */,
|
||||
);
|
||||
path = InfoPane;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
|
@ -757,6 +769,7 @@
|
|||
CABFA9BD2592EEEA00380FEE /* Environment.swift in Sources */,
|
||||
CABFA9C32592EEEA00380FEE /* Downloads.swift in Sources */,
|
||||
CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */,
|
||||
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */,
|
||||
CA378F992466567600A58CE0 /* AppState.swift in Sources */,
|
||||
CAD2E7A42449574E00113D76 /* XcodeListView.swift in Sources */,
|
||||
CAA1CB45255A5B60003FD669 /* SignIn2FAView.swift in Sources */,
|
||||
|
|
@ -768,7 +781,6 @@
|
|||
CAE4247F259A666100B8B246 /* MainWindow.swift in Sources */,
|
||||
CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */,
|
||||
CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */,
|
||||
CAC281C8259F97E100B8AB0B /* InstallationStepView.swift in Sources */,
|
||||
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */,
|
||||
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
|
||||
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */,
|
||||
|
|
@ -782,6 +794,7 @@
|
|||
CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */,
|
||||
CAFBDB952598FE96003DCC5A /* FocusedValues.swift in Sources */,
|
||||
CAC9F92D25BCDA4400B4965F /* HelperInstallState.swift in Sources */,
|
||||
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */,
|
||||
CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */,
|
||||
CABFA9C22592EEEA00380FEE /* Publisher+Resumable.swift in Sources */,
|
||||
CAFBDC68259A308B003DCC5A /* InfoPane.swift in Sources */,
|
||||
|
|
@ -797,6 +810,7 @@
|
|||
CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */,
|
||||
CABFA9C92592EEEA00380FEE /* URLRequest+Apple.swift in Sources */,
|
||||
CABFAA432593104F00380FEE /* AboutView.swift in Sources */,
|
||||
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */,
|
||||
CABFA9CC2592EEEA00380FEE /* Path+.swift in Sources */,
|
||||
CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */,
|
||||
63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ public struct Shell {
|
|||
"--stop-with-process=\(ProcessInfo.processInfo.processIdentifier)",
|
||||
"--dir=\(destination.parent.string)",
|
||||
"--out=\(destination.basename())",
|
||||
"--human-readable=false", // sets the output to use bytes instead of formatting
|
||||
url.absoluteString,
|
||||
]
|
||||
let stdOutPipe = Pipe()
|
||||
|
|
@ -52,8 +53,10 @@ public struct Shell {
|
|||
let stdErrPipe = Pipe()
|
||||
process.standardError = stdErrPipe
|
||||
|
||||
var progress = Progress(totalUnitCount: 100)
|
||||
|
||||
var progress = Progress()
|
||||
progress.kind = .file
|
||||
progress.fileOperationKind = .downloading
|
||||
|
||||
let observer = NotificationCenter.default.addObserver(
|
||||
forName: .NSFileHandleDataAvailable,
|
||||
object: nil,
|
||||
|
|
@ -68,16 +71,8 @@ public struct Shell {
|
|||
defer { handle.waitForDataInBackgroundAndNotify() }
|
||||
|
||||
let string = String(decoding: handle.availableData, as: UTF8.self)
|
||||
let regex = try! NSRegularExpression(pattern: #"((?<percent>\d+)%\))"#)
|
||||
let range = NSRange(location: 0, length: string.utf16.count)
|
||||
|
||||
guard
|
||||
let match = regex.firstMatch(in: string, options: [], range: range),
|
||||
let matchRange = Range(match.range(withName: "percent"), in: string),
|
||||
let percentCompleted = Int64(string[matchRange])
|
||||
else { return }
|
||||
|
||||
progress.completedUnitCount = percentCompleted
|
||||
|
||||
progress.updateFromAria2(string: string)
|
||||
}
|
||||
|
||||
stdOutPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
|
|
|
|||
71
Xcodes/Backend/Progress+.swift
Normal file
71
Xcodes/Backend/Progress+.swift
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import os.log
|
||||
import Foundation
|
||||
|
||||
extension Progress {
|
||||
func updateFromAria2(string: String) {
|
||||
|
||||
let range = NSRange(location: 0, length: string.utf16.count)
|
||||
|
||||
// MARK: Total Downloaded
|
||||
let regexTotalDownloaded = try! NSRegularExpression(pattern: #"(?<= )(.*)(?=\/)"#)
|
||||
|
||||
if let match = regexTotalDownloaded.firstMatch(in: string, options: [], range: range),
|
||||
let matchRange = Range(match.range(at: 0), in: string),
|
||||
let totalDownloaded = Int(string[matchRange].replacingOccurrences(of: "B", with: "")) {
|
||||
self.completedUnitCount = Int64(totalDownloaded)
|
||||
}
|
||||
|
||||
// MARK: Filesize
|
||||
let regexTotalFileSize = try! NSRegularExpression(pattern: #"(?<=/)(.*)(?=\()"#)
|
||||
|
||||
if let match = regexTotalFileSize.firstMatch(in: string, options: [], range: range),
|
||||
let matchRange = Range(match.range(at: 0), in: string),
|
||||
let totalFileSize = Int(string[matchRange].replacingOccurrences(of: "B", with: "")) {
|
||||
|
||||
if totalFileSize > 0 {
|
||||
self.totalUnitCount = Int64(totalFileSize)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: PERCENT DOWNLOADED
|
||||
// Since we get fractionCompleted from completedUnitCount + totalUnitCount, no need to process
|
||||
// let regexPercent = try! NSRegularExpression(pattern: #"((?<percent>\d+)%\))"#)
|
||||
|
||||
// MARK: Speed
|
||||
let regexSpeed = try! NSRegularExpression(pattern: #"(?<=DL:)(.*)(?= )"#)
|
||||
|
||||
if let match = regexSpeed.firstMatch(in: string, options: [], range: range),
|
||||
let matchRange = Range(match.range(at: 0), in: string),
|
||||
let speed = Int(string[matchRange].replacingOccurrences(of: "B", with: "")) {
|
||||
self.throughput = speed
|
||||
} else {
|
||||
Logger.appState.debug("Could not parse throughput from aria2 download output")
|
||||
}
|
||||
|
||||
// MARK: Estimated Time Remaining
|
||||
let regexETA = try! NSRegularExpression(pattern: #"(?<=ETA:)(?<hours>\d*h)?(?<minutes>\d*m)?(?<seconds>\d*s)?"#)
|
||||
|
||||
if let match = regexETA.firstMatch(in: string, options: [], range: range) {
|
||||
var seconds: Int = 0
|
||||
|
||||
if let matchRange = Range(match.range(withName: "hours"), in: string),
|
||||
let hours = Int(string[matchRange].replacingOccurrences(of: "h", with: "")) {
|
||||
seconds += (hours * 60 * 60)
|
||||
}
|
||||
|
||||
if let matchRange = Range(match.range(withName: "minutes"), in: string),
|
||||
let minutes = Int(string[matchRange].replacingOccurrences(of: "m", with: "")) {
|
||||
seconds += (minutes * 60)
|
||||
}
|
||||
|
||||
if let matchRange = Range(match.range(withName: "seconds"), in: string),
|
||||
let second = Int(string[matchRange].replacingOccurrences(of: "s", with: "")) {
|
||||
seconds += (second)
|
||||
}
|
||||
|
||||
self.estimatedTimeRemaining = TimeInterval(seconds)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -8,16 +8,19 @@ import SwiftUI
|
|||
public struct ObservingProgressIndicator: View {
|
||||
let controlSize: NSControl.ControlSize
|
||||
let style: NSProgressIndicator.Style
|
||||
let showsAdditionalDescription: Bool
|
||||
@StateObject private var progress: ProgressWrapper
|
||||
|
||||
public init(
|
||||
_ progress: Progress,
|
||||
controlSize: NSControl.ControlSize,
|
||||
style: NSProgressIndicator.Style
|
||||
style: NSProgressIndicator.Style,
|
||||
showsAdditionalDescription: Bool = false
|
||||
) {
|
||||
_progress = StateObject(wrappedValue: ProgressWrapper(progress: progress))
|
||||
self.controlSize = controlSize
|
||||
self.style = style
|
||||
self.showsAdditionalDescription = showsAdditionalDescription
|
||||
}
|
||||
|
||||
class ProgressWrapper: ObservableObject {
|
||||
|
|
@ -26,23 +29,31 @@ public struct ObservingProgressIndicator: View {
|
|||
|
||||
init(progress: Progress) {
|
||||
self.progress = progress
|
||||
cancellable = progress
|
||||
.publisher(for: \.fractionCompleted)
|
||||
.receive(on: RunLoop.main)
|
||||
cancellable = progress.publisher(for: \.fractionCompleted)
|
||||
.combineLatest(progress.publisher(for: \.localizedAdditionalDescription))
|
||||
.throttle(for: 1.0, scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { [weak self] _ in self?.objectWillChange.send() }
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ProgressIndicator(
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
doubleValue: progress.progress.fractionCompleted,
|
||||
controlSize: controlSize,
|
||||
isIndeterminate: progress.progress.isIndeterminate,
|
||||
style: style
|
||||
)
|
||||
.help("Downloading: \(Int((progress.progress.fractionCompleted * 100)))% complete")
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ProgressIndicator(
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
doubleValue: progress.progress.fractionCompleted,
|
||||
controlSize: controlSize,
|
||||
isIndeterminate: progress.progress.isIndeterminate,
|
||||
style: style
|
||||
)
|
||||
.help("Downloading: \(Int((progress.progress.fractionCompleted * 100)))% complete")
|
||||
|
||||
if showsAdditionalDescription, progress.progress.localizedAdditionalDescription.isEmpty == false {
|
||||
Text(progress.progress.localizedAdditionalDescription)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,6 +68,20 @@ struct ObservingProgressBar_Previews: PreviewProvider {
|
|||
controlSize: .small,
|
||||
style: .spinning
|
||||
)
|
||||
|
||||
ObservingProgressIndicator(
|
||||
configure(Progress()) {
|
||||
$0.kind = .file
|
||||
$0.fileOperationKind = .downloading
|
||||
$0.estimatedTimeRemaining = 123
|
||||
$0.totalUnitCount = 11944848484
|
||||
$0.completedUnitCount = 848444920
|
||||
$0.throughput = 9211681
|
||||
},
|
||||
controlSize: .regular,
|
||||
style: .bar,
|
||||
showsAdditionalDescription: true
|
||||
)
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,51 +13,53 @@ struct InfoPane: View {
|
|||
var body: some View {
|
||||
if let xcode = appState.allXcodes.first(where: { $0.id == selectedXcodeID }) {
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
icon(for: xcode)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
|
||||
Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)")
|
||||
.font(.title)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)")
|
||||
.font(.title)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
switch xcode.installState {
|
||||
case .notInstalled:
|
||||
InstallButton(xcode: xcode)
|
||||
downloadFileSize(for: xcode)
|
||||
case .installing(let installationStep):
|
||||
InstallationStepDetailView(installationStep: installationStep)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
CancelInstallButton(xcode: xcode)
|
||||
case let .installed(path):
|
||||
HStack {
|
||||
Text(path.string)
|
||||
Button(action: { appState.reveal(id: xcode.id) }) {
|
||||
Image(systemName: "arrow.right.circle.fill")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.help("Reveal in Finder")
|
||||
}
|
||||
|
||||
switch xcode.installState {
|
||||
case .notInstalled:
|
||||
InstallButton(xcode: xcode)
|
||||
case .installing:
|
||||
CancelInstallButton(xcode: xcode)
|
||||
case let .installed(path):
|
||||
HStack {
|
||||
Text(path.string)
|
||||
Button(action: { appState.reveal(id: xcode.id) }) {
|
||||
Image(systemName: "arrow.right.circle.fill")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.help("Reveal in Finder")
|
||||
}
|
||||
HStack {
|
||||
SelectButton(xcode: xcode)
|
||||
.disabled(xcode.selected)
|
||||
.help("Selected")
|
||||
|
||||
HStack {
|
||||
SelectButton(xcode: xcode)
|
||||
.disabled(xcode.selected)
|
||||
.help("Selected")
|
||||
|
||||
OpenButton(xcode: xcode)
|
||||
.help("Open")
|
||||
|
||||
Spacer()
|
||||
UninstallButton(xcode: xcode)
|
||||
}
|
||||
OpenButton(xcode: xcode)
|
||||
.help("Open")
|
||||
|
||||
Spacer()
|
||||
UninstallButton(xcode: xcode)
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
releaseNotes(for: xcode)
|
||||
identicalBuilds(for: xcode)
|
||||
compatibility(for: xcode)
|
||||
sdks(for: xcode)
|
||||
compilers(for: xcode)
|
||||
downloadFileSize(for: xcode)
|
||||
|
||||
Group{
|
||||
releaseNotes(for: xcode)
|
||||
identicalBuilds(for: xcode)
|
||||
compatibility(for: xcode)
|
||||
sdks(for: xcode)
|
||||
compilers(for: xcode)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
@ -210,7 +212,6 @@ struct InfoPane: View {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
private var empty: some View {
|
||||
Text("No Xcode Selected")
|
||||
|
|
@ -252,7 +253,7 @@ struct InfoPane_Previews: PreviewProvider {
|
|||
]
|
||||
})
|
||||
.previewDisplayName("Populated, Installed, Selected")
|
||||
|
||||
|
||||
InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 0))
|
||||
.environmentObject(configure(AppState()) {
|
||||
$0.allXcodes = [
|
||||
|
|
@ -278,7 +279,7 @@ struct InfoPane_Previews: PreviewProvider {
|
|||
]
|
||||
})
|
||||
.previewDisplayName("Populated, Installed, Unselected")
|
||||
|
||||
|
||||
InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 0))
|
||||
.environmentObject(configure(AppState()) {
|
||||
$0.allXcodes = [
|
||||
|
|
@ -304,7 +305,7 @@ struct InfoPane_Previews: PreviewProvider {
|
|||
]
|
||||
})
|
||||
.previewDisplayName("Populated, Uninstalled")
|
||||
|
||||
|
||||
InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"]))
|
||||
.environmentObject(configure(AppState()) {
|
||||
$0.allXcodes = [
|
||||
|
|
@ -318,6 +319,20 @@ struct InfoPane_Previews: PreviewProvider {
|
|||
]
|
||||
})
|
||||
.previewDisplayName("Basic, installed")
|
||||
|
||||
InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"]))
|
||||
.environmentObject(configure(AppState()) {
|
||||
$0.allXcodes = [
|
||||
.init(
|
||||
version: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"]),
|
||||
installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 232323232; $0.fileCompletedCount = 2323004; $0.fileTotalCount = 1193939393 })),
|
||||
selected: false,
|
||||
icon: nil,
|
||||
sdks: nil,
|
||||
compilers: nil)
|
||||
]
|
||||
})
|
||||
.previewDisplayName("Basic, installing")
|
||||
|
||||
InfoPane(selectedXcodeID: nil)
|
||||
.environmentObject(configure(AppState()) {
|
||||
48
Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift
Normal file
48
Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import SwiftUI
|
||||
|
||||
struct InstallationStepDetailView: View {
|
||||
let installationStep: InstallationStep
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("Step \(installationStep.stepNumber) of \(installationStep.stepCount): \(installationStep.message)")
|
||||
|
||||
switch installationStep {
|
||||
case let .downloading(progress):
|
||||
ObservingProgressIndicator(
|
||||
progress,
|
||||
controlSize: .regular,
|
||||
style: .bar,
|
||||
showsAdditionalDescription: true
|
||||
)
|
||||
|
||||
case .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
|
||||
ProgressView()
|
||||
.scaleEffect(0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InstallDetailView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
InstallationStepDetailView(
|
||||
installationStep: .downloading(
|
||||
progress: configure(Progress()) {
|
||||
$0.kind = .file
|
||||
$0.fileOperationKind = .downloading
|
||||
$0.estimatedTimeRemaining = 123
|
||||
$0.totalUnitCount = 11944848484
|
||||
$0.completedUnitCount = 848444920
|
||||
$0.throughput = 9211681
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
InstallationStepDetailView(
|
||||
installationStep: .unarchiving
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import SwiftUI
|
||||
|
||||
struct InstallationStepView: View {
|
||||
struct InstallationStepRowView: View {
|
||||
let installationStep: InstallationStep
|
||||
let highlighted: Bool
|
||||
let cancel: () -> Void
|
||||
|
|
@ -42,7 +42,7 @@ struct InstallView_Previews: PreviewProvider {
|
|||
Group {
|
||||
ForEach(ColorScheme.allCases, id: \.self) { colorScheme in
|
||||
Group {
|
||||
InstallationStepView(
|
||||
InstallationStepRowView(
|
||||
installationStep: .downloading(
|
||||
progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 }
|
||||
),
|
||||
|
|
@ -50,31 +50,31 @@ struct InstallView_Previews: PreviewProvider {
|
|||
cancel: {}
|
||||
)
|
||||
|
||||
InstallationStepView(
|
||||
InstallationStepRowView(
|
||||
installationStep: .unarchiving,
|
||||
highlighted: false,
|
||||
cancel: {}
|
||||
)
|
||||
|
||||
InstallationStepView(
|
||||
InstallationStepRowView(
|
||||
installationStep: .moving(destination: "/Applications"),
|
||||
highlighted: false,
|
||||
cancel: {}
|
||||
)
|
||||
|
||||
InstallationStepView(
|
||||
InstallationStepRowView(
|
||||
installationStep: .trashingArchive,
|
||||
highlighted: false,
|
||||
cancel: {}
|
||||
)
|
||||
|
||||
InstallationStepView(
|
||||
InstallationStepRowView(
|
||||
installationStep: .checkingSecurity,
|
||||
highlighted: false,
|
||||
cancel: {}
|
||||
)
|
||||
|
||||
InstallationStepView(
|
||||
InstallationStepRowView(
|
||||
installationStep: .finishing,
|
||||
highlighted: false,
|
||||
cancel: {}
|
||||
|
|
@ -87,7 +87,7 @@ struct InstallView_Previews: PreviewProvider {
|
|||
|
||||
ForEach(ColorScheme.allCases, id: \.self) { colorScheme in
|
||||
Group {
|
||||
InstallationStepView(
|
||||
InstallationStepRowView(
|
||||
installationStep: .downloading(
|
||||
progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 }
|
||||
),
|
||||
|
|
@ -109,7 +109,7 @@ struct XcodeListViewRow: View {
|
|||
.buttonStyle(AppStoreButtonStyle(primary: false, highlighted: selected))
|
||||
.help("Install this version")
|
||||
case let .installing(installationStep):
|
||||
InstallationStepView(
|
||||
InstallationStepRowView(
|
||||
installationStep: installationStep,
|
||||
highlighted: selected,
|
||||
cancel: { appState.xcodeBeingConfirmedForInstallCancellation = xcode }
|
||||
|
|
|
|||
Loading…
Reference in a new issue