Add refresh indicator to the Timeline. Issue #15

This commit is contained in:
Maurice Parker 2020-01-03 14:23:37 -07:00
parent 5a7863d447
commit 5cd163e1e4
4 changed files with 84 additions and 24 deletions

View File

@ -119,13 +119,20 @@
</connections>
</tableView>
<toolbarItems>
<barButtonItem title="Mark All as Read" id="fTv-eX-72r">
<barButtonItem image="asterisk.circle" catalog="system" id="fTv-eX-72r">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="accLabelText" value="Mark All as Read"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="markAllAsRead:" destination="Kyk-vK-QRX" id="4nd-Gg-APm"/>
</connections>
</barButtonItem>
<barButtonItem style="plain" systemItem="flexibleSpace" id="53V-wq-bat"/>
<barButtonItem style="plain" systemItem="flexibleSpace" id="93y-8j-WBh"/>
<barButtonItem title="First Unread" id="2v2-jD-C9k">
<barButtonItem image="chevron.down.circle" catalog="system" id="2v2-jD-C9k">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="accLabelText" value="First Unread"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="firstUnread:" destination="Kyk-vK-QRX" id="d5y-x5-Qht"/>
</connections>
@ -360,6 +367,7 @@
</scene>
</scenes>
<resources>
<image name="asterisk.circle" catalog="system" width="64" height="60"/>
<image name="chevron.down" catalog="system" width="64" height="36"/>
<image name="chevron.down.circle" catalog="system" width="64" height="60"/>
<image name="chevron.up" catalog="system" width="64" height="36"/>

View File

@ -669,10 +669,16 @@ private extension MasterFeedViewController {
self.refreshProgressView = refreshProgressView
let spaceItemButton1 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let refreshProgressItemButton = UIBarButtonItem(customView: refreshProgressView)
let spaceItemButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let spaceItemButton2 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
addNewItemButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add(_:)))
setToolbarItems([refreshProgressItemButton, spaceItemButton, addNewItemButton], animated: false)
setToolbarItems([spaceItemButton1,
refreshProgressItemButton,
spaceItemButton2,
addNewItemButton
], animated: false)
}
func updateUI() {

View File

@ -14,33 +14,40 @@ class RefreshProgressView: UIView {
@IBOutlet weak var progressView: UIProgressView!
@IBOutlet weak var label: UILabel!
private lazy var progressWidth = progressView.widthAnchor.constraint(equalToConstant: 100.0)
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
private var lastLabelDisplayedTime: Date? = nil
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
override func awakeFromNib() {
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
if !AccountManager.shared.combinedRefreshProgress.isComplete {
progressChanged()
} else {
updateRefreshLabel()
}
}
func updateRefreshLabel() {
if let accountLastArticleFetchEndTime = AccountManager.shared.lastArticleFetchEndTime {
if let lastLabelDisplayedTime = lastLabelDisplayedTime, lastLabelDisplayedTime.addingTimeInterval(2) > Date() {
return
}
lastLabelDisplayedTime = Date()
if Date() > accountLastArticleFetchEndTime.addingTimeInterval(1) {
let relativeDateTimeFormatter = RelativeDateTimeFormatter()
relativeDateTimeFormatter.dateTimeStyle = .named
let refreshed = relativeDateTimeFormatter.localizedString(for: accountLastArticleFetchEndTime, relativeTo: Date())
let localizedRefreshText = NSLocalizedString("Updated %@", comment: "Updated")
let refreshText = NSString.localizedStringWithFormat(localizedRefreshText as NSString, refreshed) as String
label.text = refreshText
} else {
label.text = NSLocalizedString("Updated just now", comment: "Updated Just Now")
}
} else {
label.text = ""
}
@ -48,7 +55,20 @@ class RefreshProgressView: UIView {
}
@objc func progressDidChange(_ note: Notification) {
progressChanged()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
// MARK: Private
private extension RefreshProgressView {
func progressChanged() {
let progress = AccountManager.shared.combinedRefreshProgress
if progress.isComplete {
@ -60,18 +80,12 @@ class RefreshProgressView: UIView {
self.progressWidth.isActive = false
}
} else {
lastLabelDisplayedTime = nil
label.isHidden = true
progressView.isHidden = false
self.progressWidth.isActive = true
let percent = Float(progress.numberCompleted) / Float(progress.numberOfTasks)
progressView.progress = percent
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}

View File

@ -17,6 +17,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
private var iconSize = IconSize.medium
private lazy var feedTapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:)))
private var refreshProgressView: RefreshProgressView?
@IBOutlet weak var filterButton: UIBarButtonItem!
@IBOutlet weak var markAllAsReadButton: UIBarButtonItem!
@IBOutlet weak var firstUnreadButton: UIBarButtonItem!
@ -72,7 +74,11 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView {
navigationItem.titleView = titleView
}
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
configureToolbar()
resetUI(resetScroll: true)
// Load the table and then scroll to the saved position if available
@ -130,6 +136,15 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
coordinator.selectFirstUnread()
}
@objc func refreshAccounts(_ sender: Any) {
refreshControl?.endRefreshing()
// This is a hack to make sure that an error dialog doesn't interfere with dismissing the refreshControl.
// If the error dialog appears too closely to the call to endRefreshing, then the refreshControl never disappears.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present(self))
}
}
// MARK: Keyboard shortcuts
@objc func selectNextUp(_ sender: Any?) {
@ -516,6 +531,22 @@ extension MasterTimelineViewController: UISearchBarDelegate {
private extension MasterTimelineViewController {
func configureToolbar() {
if coordinator.isThreePanelMode {
firstUnreadButton.isHidden = true
return
}
guard let refreshProgressView = Bundle.main.loadNibNamed("RefreshProgressView", owner: self, options: nil)?[0] as? RefreshProgressView else {
return
}
self.refreshProgressView = refreshProgressView
let refreshProgressItemButton = UIBarButtonItem(customView: refreshProgressView)
toolbarItems?.insert(refreshProgressItemButton, at: 2)
}
func resetUI(resetScroll: Bool) {
title = coordinator.timelineFeed?.nameForDisplay ?? "Timeline"
@ -558,6 +589,7 @@ private extension MasterTimelineViewController {
}
func updateUI() {
refreshProgressView?.updateRefreshLabel()
updateTitleUnreadCount()
updateToolbar()
}