From 91e009932b8a6f2fe7331d667f6984dcd8d73860 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 23 Apr 2019 07:26:35 -0500 Subject: [PATCH] Change progress indicator to be a progress bar instead of the activity indicator. --- NetNewsWire.xcodeproj/project.pbxproj | 20 +++ iOS/Detail/DetailViewController.swift | 22 ++- iOS/Master/MasterViewController.swift | 12 +- iOS/Progress/NavigationProgressView.swift | 132 ++++++++++++++++ .../ProgressTableViewController.swift | 27 ++++ .../UINavigationController+Progress.swift | 145 ++++++++++++++++++ .../MasterTimelineViewController.swift | 12 +- 7 files changed, 349 insertions(+), 21 deletions(-) create mode 100644 iOS/Progress/NavigationProgressView.swift create mode 100644 iOS/Progress/ProgressTableViewController.swift create mode 100644 iOS/Progress/UINavigationController+Progress.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index a6fb9a0bd..6c669259a 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -19,6 +19,9 @@ 512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; + 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */; }; + 5183CCDF226F1FCC0010922C /* UINavigationController+Progress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDE226F1FCC0010922C /* UINavigationController+Progress.swift */; }; + 5183CCE3226F314C0010922C /* ProgressTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE2226F314C0010922C /* ProgressTableViewController.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -611,6 +614,9 @@ 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISplitViewController-Extensions.swift"; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; + 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProgressView.swift; sourceTree = ""; }; + 5183CCDE226F1FCC0010922C /* UINavigationController+Progress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Progress.swift"; sourceTree = ""; }; + 5183CCE2226F314C0010922C /* ProgressTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressTableViewController.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = ""; }; 51C4524F226506F400C03939 /* UIImage-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage-Extensions.swift"; sourceTree = ""; }; @@ -903,6 +909,16 @@ path = Tree; sourceTree = ""; }; + 5183CCDB226F1EEB0010922C /* Progress */ = { + isa = PBXGroup; + children = ( + 5183CCE2226F314C0010922C /* ProgressTableViewController.swift */, + 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */, + 5183CCDE226F1FCC0010922C /* UINavigationController+Progress.swift */, + ); + path = Progress; + sourceTree = ""; + }; 51C45245226506C800C03939 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1522,6 +1538,7 @@ 51C4526D2265091600C03939 /* Timeline */, 51C4527D2265092C00C03939 /* Detail */, 51C452802265093600C03939 /* Add */, + 5183CCDB226F1EEB0010922C /* Progress */, 51C45245226506C800C03939 /* Extensions */, 84C9FC9A2262A1A900D921D6 /* Resources */, ); @@ -2145,6 +2162,7 @@ 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */, 51C45269226508F600C03939 /* MasterTableViewCell.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, + 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */, 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 51C452A122650A1900C03939 /* FeaturedImageDownloader.swift in Sources */, @@ -2156,6 +2174,7 @@ 5126EE97226CB48A00C22AFC /* NavigationStateController.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, + 5183CCDF226F1FCC0010922C /* UINavigationController+Progress.swift in Sources */, 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, 512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */, 51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */, @@ -2186,6 +2205,7 @@ 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */, 51C4527A2265091600C03939 /* SingleLineUILabelSizer.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, + 5183CCE3226F314C0010922C /* ProgressTableViewController.swift in Sources */, 512E09012268907400BDCFDD /* MasterTableViewSectionHeader.swift in Sources */, 51C45268226508F600C03939 /* MasterUnreadCountView.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index e8101eef3..276226a94 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -37,9 +37,15 @@ class DetailViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(articleSelectionDidChange(_:)), name: .ArticleSelectionDidChange, object: navState) - + NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) + } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + updateProgressIndicatorIfNeeded() + } + func markAsRead() { if let article = navState?.currentArticle { markArticles(Set([article]), statusKey: .read, flag: true) @@ -107,6 +113,10 @@ class DetailViewController: UIViewController { updateUI() reloadHTML() } + + @objc func progressDidChange(_ note: Notification) { + updateProgressIndicatorIfNeeded() + } // MARK: Actions @@ -204,3 +214,13 @@ extension DetailViewController: WKNavigationDelegate { } } + +private extension DetailViewController { + + func updateProgressIndicatorIfNeeded() { + if !(UIDevice.current.userInterfaceIdiom == .pad) { + navigationController?.updateAccountRefreshProgressIndicator() + } + } + +} diff --git a/iOS/Master/MasterViewController.swift b/iOS/Master/MasterViewController.swift index 21447bbab..99533b0d7 100644 --- a/iOS/Master/MasterViewController.swift +++ b/iOS/Master/MasterViewController.swift @@ -12,7 +12,7 @@ import Articles import RSCore import RSTree -class MasterViewController: UITableViewController, UndoableCommandRunner { +class MasterViewController: ProgressTableViewController, UndoableCommandRunner { @IBOutlet weak var markAllAsReadButton: UIBarButtonItem! @@ -35,7 +35,6 @@ class MasterViewController: UITableViewController, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(backingStoresDidRebuild(_:)), name: .BackingStoresDidRebuild, object: navState) NotificationCenter.default.addObserver(self, selector: #selector(masterSelectionDidChange(_:)), name: .MasterSelectionDidChange, object: navState) @@ -68,14 +67,6 @@ class MasterViewController: UITableViewController, UndoableCommandRunner { tableView.reloadData() } - @objc dynamic func progressDidChange(_ notification: Notification) { - if AccountManager.shared.combinedRefreshProgress.isComplete { - refreshControl?.endRefreshing() - } else { - refreshControl?.beginRefreshing() - } - } - @objc func unreadCountDidChange(_ note: Notification) { guard let representedObject = note.object else { @@ -626,6 +617,7 @@ private extension MasterViewController { @objc private func refreshAccounts(_ sender: Any) { AccountManager.shared.refreshAll() + refreshControl?.endRefreshing() } func updateUI() { diff --git a/iOS/Progress/NavigationProgressView.swift b/iOS/Progress/NavigationProgressView.swift new file mode 100644 index 000000000..be281a047 --- /dev/null +++ b/iOS/Progress/NavigationProgressView.swift @@ -0,0 +1,132 @@ +// +// NavigationProgressView.swift +// KYNavigationProgress +// +// Created by kyo__hei on 2015/12/29. +// Copyright (c) 2015 kyo__hei. All rights reserved. +// +// Original project: https://github.com/ykyouhei/KYNavigationProgress + +import UIKit + +public final class NavigationProgressView: UIView { + + /* ====================================================================== */ + // MARK: - Properties + /* ====================================================================== */ + + internal var progress: Float = 0 { + didSet { + progress = min(1, progress) + barWidthConstraint.constant = bounds.width * CGFloat(progress) + } + } + + internal let bar = UIView() + + @objc public dynamic var progressTintColor: UIColor? = UIColor(red: 0, green: 122/255, blue: 1, alpha: 1) { + didSet { + bar.backgroundColor = progressTintColor + } + } + + @objc public dynamic var trackTintColor: UIColor? = .clear { + didSet { + backgroundColor = trackTintColor + } + } + + fileprivate let barWidthConstraint: NSLayoutConstraint + + override public var frame: CGRect { + didSet { + let tmpProgress = progress + progress = tmpProgress + } + } + + + /* ====================================================================== */ + // MARK: - initializer + /* ====================================================================== */ + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(frame: CGRect) { + barWidthConstraint = NSLayoutConstraint( + item: bar, + attribute: .width, + relatedBy: .equal, + toItem: nil, + attribute: .notAnAttribute, + multiplier: 1, + constant: frame.width * CGFloat(progress)) + + super.init(frame: frame) + + let leftConstraint = NSLayoutConstraint( + item: bar, + attribute: .left, + relatedBy: .equal, + toItem: self, + attribute: .left, + multiplier: 1, + constant: 0) + + let bottomConstraint = NSLayoutConstraint( + item: bar, + attribute: .bottom, + relatedBy: .equal, + toItem: self, + attribute: .bottom, + multiplier: 1, + constant: 0) + + let topConstraint = NSLayoutConstraint( + item: bar, + attribute: .top, + relatedBy: .equal, + toItem: self, + attribute: .top, + multiplier: 1, + constant: 0) + + addSubview(bar) + + backgroundColor = trackTintColor + + bar.backgroundColor = progressTintColor + bar.translatesAutoresizingMaskIntoConstraints = false + addConstraints([ + barWidthConstraint, + leftConstraint, + topConstraint, + bottomConstraint]) + } + + + /* ====================================================================== */ + // MARK: - Notification + /* ====================================================================== */ + + func deviceDidRotate(_ notification: Notification) { + } + + + /* ====================================================================== */ + // MARK: - Method + /* ====================================================================== */ + + internal func setProgress(_ progress: Float, animated: Bool) { + let duration: TimeInterval = animated ? 0.1 : 0 + + self.progress = progress + + UIView.animate(withDuration: duration, animations: { + self.layoutIfNeeded() + }) + } + +} diff --git a/iOS/Progress/ProgressTableViewController.swift b/iOS/Progress/ProgressTableViewController.swift new file mode 100644 index 000000000..216f3f127 --- /dev/null +++ b/iOS/Progress/ProgressTableViewController.swift @@ -0,0 +1,27 @@ +// +// ProgressTableViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/23/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class ProgressTableViewController: UITableViewController { + + override func viewDidLoad() { + super.viewDidLoad() + NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + navigationController?.updateAccountRefreshProgressIndicator() + } + + @objc func progressDidChange(_ note: Notification) { + navigationController?.updateAccountRefreshProgressIndicator() + } + +} diff --git a/iOS/Progress/UINavigationController+Progress.swift b/iOS/Progress/UINavigationController+Progress.swift new file mode 100644 index 000000000..bad5e6890 --- /dev/null +++ b/iOS/Progress/UINavigationController+Progress.swift @@ -0,0 +1,145 @@ +// +// UINavigationController+Progress.swift +// KYNavigationProgress +// +// Created by kyo__hei on 2015/12/29. +// Copyright (c) 2015 kyo__hei. All rights reserved. +// +// Original project: https://github.com/ykyouhei/KYNavigationProgress + +import UIKit +import Account + +private let constraintIdentifier = "progressHeightConstraint" + +public extension UINavigationController { + + /* ====================================================================== */ + // MARK: - Properties + /* ====================================================================== */ + + /** + Default is 2.0 + */ + var progressHeight: CGFloat { + get { return progressView.frame.height } + set { + progressView.frame.origin.y = navigationBar.frame.height - newValue + progressView.frame.size.height = newValue + } + } + + /** + The color shown for the portion of the progress bar that is not filled. + default is clear color. + */ + var trackTintColor: UIColor? { + get { return progressView.trackTintColor } + set { progressView.trackTintColor = newValue } + } + + /** + The color shown for the portion of the progress bar that is filled. + default is (r: 0, g: 122, b: 225, a: 255. + */ + var progressTintColor: UIColor? { + get { return progressView.progressTintColor } + set { progressView.progressTintColor = newValue } + } + + /** + The current progress is represented by a floating-point value between 0.0 and 1.0, + inclusive, where 1.0 indicates the completion of the task. The default value is 0.0. + */ + var progress: Float { + get { return progressView.progress } + set { progressView.progress = newValue } + } + + + private var progressView: NavigationProgressView { + + for subview in navigationBar.subviews { + if let progressView = subview as? NavigationProgressView { + return progressView + } + } + + let defaultHeight = CGFloat(2) + let frame = CGRect( + x: 0, + y: navigationBar.frame.height - defaultHeight, + width: navigationBar.frame.width, + height: defaultHeight + ) + let progressView = NavigationProgressView(frame: frame) + + navigationBar.addSubview(progressView) + + progressView.autoresizingMask = [ + .flexibleWidth, .flexibleTopMargin + ] + + + return progressView + } + + + /* ====================================================================== */ + // MARK: - Public Method + /* ====================================================================== */ + + /** + Adjusts the current progress shown by the receiver, optionally animating the change. + + - parameter progress: The new progress value. + - parameter animated: true if the change should be animated, false if the change should happen immediately. + */ + func setProgress(_ progress: Float, animated: Bool) { + progressView.bar.alpha = 1 + progressView.setProgress(progress, animated: animated) + } + + /** + While progress is changed to 1.0, the bar will fade out. After that, progress will be 0.0. + */ + func finishProgress() { + progressView.bar.alpha = 1 + progressView.setProgress(1, animated: true) + + UIView.animate(withDuration: 0.25, + animations: { + self.progressView.bar.alpha = 0 + }, completion: { finished in + self.progressView.progress = 0 + } + ) + } + + /** + While progress is changed to 0.0, the bar will fade out. + */ + func cancelProgress() { + progressView.setProgress(0, animated: true) + + UIView.animate(withDuration: 0.25, animations: { + self.progressView.bar.alpha = 0 + }) + } + + func updateAccountRefreshProgressIndicator() { + + let progress = AccountManager.shared.combinedRefreshProgress + + if progress.isComplete { + if self.progress != 0 { + finishProgress() + } + } else { + let percent = Float(progress.numberCompleted) / Float(progress.numberOfTasks) + setProgress(percent, animated: true) + } + + } + +} diff --git a/iOS/Timeline/MasterTimelineViewController.swift b/iOS/Timeline/MasterTimelineViewController.swift index 7abd6e657..0191ac964 100644 --- a/iOS/Timeline/MasterTimelineViewController.swift +++ b/iOS/Timeline/MasterTimelineViewController.swift @@ -11,7 +11,7 @@ import RSCore import Account import Articles -class MasterTimelineViewController: UITableViewController, UndoableCommandRunner { +class MasterTimelineViewController: ProgressTableViewController, UndoableCommandRunner { private var rowHeightWithFeedName: CGFloat = 0.0 private var rowHeightWithoutFeedName: CGFloat = 0.0 @@ -41,7 +41,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(articlesReinitialized(_:)), name: .ArticlesReinitialized, object: navState) NotificationCenter.default.addObserver(self, selector: #selector(articleDataDidChange(_:)), name: .ArticleDataDidChange, object: navState) @@ -184,14 +183,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner // MARK: Notifications - @objc dynamic func progressDidChange(_ notification: Notification) { - if AccountManager.shared.combinedRefreshProgress.isComplete { - refreshControl?.endRefreshing() - } else { - refreshControl?.beginRefreshing() - } - } - @objc dynamic func unreadCountDidChange(_ notification: Notification) { updateUI() } @@ -350,6 +341,7 @@ private extension MasterTimelineViewController { @objc private func refreshAccounts(_ sender: Any) { AccountManager.shared.refreshAll() + refreshControl?.endRefreshing() } func resetUI() {