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> </connections>
</tableView> </tableView>
<toolbarItems> <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> <connections>
<action selector="markAllAsRead:" destination="Kyk-vK-QRX" id="4nd-Gg-APm"/> <action selector="markAllAsRead:" destination="Kyk-vK-QRX" id="4nd-Gg-APm"/>
</connections> </connections>
</barButtonItem> </barButtonItem>
<barButtonItem style="plain" systemItem="flexibleSpace" id="53V-wq-bat"/>
<barButtonItem style="plain" systemItem="flexibleSpace" id="93y-8j-WBh"/> <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> <connections>
<action selector="firstUnread:" destination="Kyk-vK-QRX" id="d5y-x5-Qht"/> <action selector="firstUnread:" destination="Kyk-vK-QRX" id="d5y-x5-Qht"/>
</connections> </connections>
@ -360,6 +367,7 @@
</scene> </scene>
</scenes> </scenes>
<resources> <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" catalog="system" width="64" height="36"/>
<image name="chevron.down.circle" catalog="system" width="64" height="60"/> <image name="chevron.down.circle" catalog="system" width="64" height="60"/>
<image name="chevron.up" catalog="system" width="64" height="36"/> <image name="chevron.up" catalog="system" width="64" height="36"/>

View File

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

View File

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

View File

@ -17,6 +17,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
private var iconSize = IconSize.medium private var iconSize = IconSize.medium
private lazy var feedTapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:))) private lazy var feedTapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:)))
private var refreshProgressView: RefreshProgressView?
@IBOutlet weak var filterButton: UIBarButtonItem! @IBOutlet weak var filterButton: UIBarButtonItem!
@IBOutlet weak var markAllAsReadButton: UIBarButtonItem! @IBOutlet weak var markAllAsReadButton: UIBarButtonItem!
@IBOutlet weak var firstUnreadButton: UIBarButtonItem! @IBOutlet weak var firstUnreadButton: UIBarButtonItem!
@ -73,6 +75,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
navigationItem.titleView = titleView navigationItem.titleView = titleView
} }
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
configureToolbar()
resetUI(resetScroll: true) resetUI(resetScroll: true)
// Load the table and then scroll to the saved position if available // Load the table and then scroll to the saved position if available
@ -130,6 +136,15 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
coordinator.selectFirstUnread() 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 // MARK: Keyboard shortcuts
@objc func selectNextUp(_ sender: Any?) { @objc func selectNextUp(_ sender: Any?) {
@ -516,6 +531,22 @@ extension MasterTimelineViewController: UISearchBarDelegate {
private extension MasterTimelineViewController { 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) { func resetUI(resetScroll: Bool) {
title = coordinator.timelineFeed?.nameForDisplay ?? "Timeline" title = coordinator.timelineFeed?.nameForDisplay ?? "Timeline"
@ -558,6 +589,7 @@ private extension MasterTimelineViewController {
} }
func updateUI() { func updateUI() {
refreshProgressView?.updateRefreshLabel()
updateTitleUnreadCount() updateTitleUnreadCount()
updateToolbar() updateToolbar()
} }