mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
Download stats for Aria2
This commit is contained in:
parent
2329098601
commit
b06be4e3fa
9 changed files with 270 additions and 38 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,11 @@
|
|||
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 */; };
|
||||
E8F0838625D873A400A4C470 /* ObservingDownloadStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -227,7 +230,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 +249,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 +262,10 @@
|
|||
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>"; };
|
||||
E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingDownloadStatsView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -387,14 +392,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 +440,7 @@
|
|||
CA61A6DF259835580008926E /* Xcode.swift */,
|
||||
CA25192925A9644800F08414 /* XcodeInstallState.swift */,
|
||||
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */,
|
||||
E87DD6EA25D053FA00D86808 /* Progress+.swift */,
|
||||
);
|
||||
path = Backend;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -444,6 +448,7 @@
|
|||
CABFAA1A2592F7D900380FEE /* Frontend */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8E98A9425D863B100EC89A0 /* InfoPane */,
|
||||
63EAA4E9259944340046AB8F /* Common */,
|
||||
CA9FF8552595082000E47BAF /* About */,
|
||||
CAFE4AAA25B7D29B0064FE51 /* Preferences */,
|
||||
|
|
@ -547,6 +552,16 @@
|
|||
path = Preferences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E8E98A9425D863B100EC89A0 /* InfoPane */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CAFBDC67259A308B003DCC5A /* InfoPane.swift */,
|
||||
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */,
|
||||
E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */,
|
||||
);
|
||||
path = InfoPane;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
|
@ -757,6 +772,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 +784,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 +797,8 @@
|
|||
CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */,
|
||||
CAFBDB952598FE96003DCC5A /* FocusedValues.swift in Sources */,
|
||||
CAC9F92D25BCDA4400B4965F /* HelperInstallState.swift in Sources */,
|
||||
E8F0838625D873A400A4C470 /* ObservingDownloadStatsView.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 +814,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()
|
||||
|
|
@ -53,7 +54,7 @@ public struct Shell {
|
|||
process.standardError = stdErrPipe
|
||||
|
||||
var progress = Progress(totalUnitCount: 100)
|
||||
|
||||
|
||||
let observer = NotificationCenter.default.addObserver(
|
||||
forName: .NSFileHandleDataAvailable,
|
||||
object: nil,
|
||||
|
|
@ -68,16 +69,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()
|
||||
|
|
|
|||
78
Xcodes/Backend/Progress+.swift
Normal file
78
Xcodes/Backend/Progress+.swift
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
|
||||
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.fileCompletedCount = totalDownloaded
|
||||
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.fileTotalCount = totalFileSize
|
||||
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 {
|
||||
print("Could not find speed")
|
||||
}
|
||||
|
||||
// MARK: Estimated Time Remaining
|
||||
let regexETA = try! NSRegularExpression(pattern: #"(?<=ETA:)(?<days>\d*d)?(?<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: "days"), in: string),
|
||||
let days = Int(string[matchRange].replacingOccurrences(of: "d", with: "")) {
|
||||
seconds += (days * 60 * 60 * 24)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ public struct ObservingProgressIndicator: View {
|
|||
init(progress: Progress) {
|
||||
self.progress = progress
|
||||
cancellable = progress
|
||||
.publisher(for: \.fractionCompleted)
|
||||
.publisher(for: \.completedUnitCount)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] _ in self?.objectWillChange.send() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,13 +51,17 @@ struct InfoPane: View {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
Group {
|
||||
downloadFileSize(for: xcode)
|
||||
downloadStats(for: xcode)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
@ -210,6 +214,19 @@ struct InfoPane: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func downloadStats(for xcode: Xcode) -> some View {
|
||||
switch xcode.installState {
|
||||
case let .installing(installationStep):
|
||||
Divider()
|
||||
InstallationStepDetailView(
|
||||
installationStep: installationStep
|
||||
)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var empty: some View {
|
||||
|
|
@ -252,7 +269,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 +295,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 +321,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 +335,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()) {
|
||||
35
Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift
Normal file
35
Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import SwiftUI
|
||||
|
||||
struct InstallationStepDetailView: View {
|
||||
let installationStep: InstallationStep
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
switch installationStep {
|
||||
case let .downloading(progress):
|
||||
Text("Step \(installationStep.stepNumber) of \(installationStep.stepCount): \(installationStep.message)")
|
||||
.font(.title2)
|
||||
ObservingDownloadStatsView(
|
||||
progress,
|
||||
controlSize: .regular,
|
||||
style: .bar
|
||||
)
|
||||
|
||||
case .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
|
||||
ProgressView()
|
||||
.scaleEffect(0.5)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 80)
|
||||
}
|
||||
}
|
||||
|
||||
struct InstallDetailView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InstallationStepDetailView(
|
||||
installationStep: .downloading(
|
||||
progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 9211681; $0.fileCompletedCount = 84844492; $0.fileTotalCount = 11944848484 }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
77
Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift
Normal file
77
Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
/// A ProgressIndicator that reflects the state of a Progress object.
|
||||
/// This functionality is already built in to ProgressView,
|
||||
/// but this implementation ensures that changes are received on the main thread.
|
||||
@available(iOS 14.0, macOS 11.0, *)
|
||||
public struct ObservingDownloadStatsView: View {
|
||||
let controlSize: NSControl.ControlSize
|
||||
let style: NSProgressIndicator.Style
|
||||
@StateObject private var progress: ProgressWrapper
|
||||
|
||||
public init(
|
||||
_ progress: Progress,
|
||||
controlSize: NSControl.ControlSize,
|
||||
style: NSProgressIndicator.Style
|
||||
) {
|
||||
_progress = StateObject(wrappedValue: ProgressWrapper(progress: progress))
|
||||
self.controlSize = controlSize
|
||||
self.style = style
|
||||
}
|
||||
|
||||
class ProgressWrapper: ObservableObject {
|
||||
var progress: Progress
|
||||
var cancellable: AnyCancellable!
|
||||
|
||||
init(progress: Progress) {
|
||||
self.progress = progress
|
||||
cancellable = progress
|
||||
.publisher(for: \.completedUnitCount)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] _ in self?.objectWillChange.send() }
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
|
||||
VStack{
|
||||
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")
|
||||
HStack {
|
||||
if let fileCompletedCount = progress.progress.fileCompletedCount, let fileTotalCount = progress.progress.fileTotalCount {
|
||||
Text("\(ByteCountFormatter.string(fromByteCount: Int64(fileCompletedCount), countStyle: .file)) of \(ByteCountFormatter.string(fromByteCount: Int64(fileTotalCount), countStyle: .file))")
|
||||
}
|
||||
if let throughput = progress.progress.throughput {
|
||||
Text(" at \(ByteCountFormatter.string(fromByteCount: Int64(throughput), countStyle: .binary))/sec")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, macOS 11.0, *)
|
||||
struct ObservingDownloadStats_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
ObservingDownloadStatsView(
|
||||
configure(Progress(totalUnitCount: 100)) {
|
||||
$0.completedUnitCount = 40
|
||||
},
|
||||
controlSize: .small,
|
||||
style: .spinning
|
||||
)
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
|
|
@ -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