diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 1c21fa9..c7d1b70 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -102,7 +102,6 @@ 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 */ @@ -265,7 +264,6 @@ E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = ""; }; E8977EA225C11E1500835F80 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = ""; }; - E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingDownloadStatsView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -557,7 +555,6 @@ children = ( CAFBDC67259A308B003DCC5A /* InfoPane.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, - E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */, ); path = InfoPane; sourceTree = ""; @@ -797,7 +794,6 @@ 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 */, diff --git a/Xcodes/Frontend/Common/ObservingProgressIndicator.swift b/Xcodes/Frontend/Common/ObservingProgressIndicator.swift index c63ea21..d4dc428 100644 --- a/Xcodes/Frontend/Common/ObservingProgressIndicator.swift +++ b/Xcodes/Frontend/Common/ObservingProgressIndicator.swift @@ -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: \.completedUnitCount) - .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) } diff --git a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift index 825fe08..e7fd41a 100644 --- a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift +++ b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift @@ -9,10 +9,11 @@ struct InstallationStepDetailView: View { case let .downloading(progress): Text("Step \(installationStep.stepNumber) of \(installationStep.stepCount): \(installationStep.message)") .font(.title2) - ObservingDownloadStatsView( + ObservingProgressIndicator( progress, controlSize: .regular, - style: .bar + style: .bar, + showsAdditionalDescription: true ) case .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing: diff --git a/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift b/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift deleted file mode 100644 index f165fb0..0000000 --- a/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift +++ /dev/null @@ -1,80 +0,0 @@ - -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{ - HStack { - 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") - Text("\(Int((progress.progress.fractionCompleted * 100)))%") - } - 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; $0.throughput = 9211681; $0.fileCompletedCount = 84844492; $0.fileTotalCount = 11944848484 - }, - controlSize: .small, - style: .bar - ) - } - .previewLayout(.sizeThatFits) - } -}