From 1469dfa56b574394008ff572e11ce6e873077b59 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 18 Feb 2021 19:10:12 -0700 Subject: [PATCH] Replace ObservingDownloadStatsView with ObservingProgressIndicator This more closely replicates the default look and feel of SwiftUI.ProgressView, but with explicit control over whether localizedAdditionalDescription is shown and without the label above the progress view that displays a fileOperationKind string. --- Xcodes.xcodeproj/project.pbxproj | 4 - .../Common/ObservingProgressIndicator.swift | 51 +++++++++--- .../InfoPane/InstallationStepDetailView.swift | 5 +- .../InfoPane/ObservingDownloadStatsView.swift | 80 ------------------- 4 files changed, 41 insertions(+), 99 deletions(-) delete mode 100644 Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift 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) - } -}