Make First Unread scroll to first unread in timeline.

Update Mark As Read and other unread dependent UI respond to unread count changing.
This commit is contained in:
Maurice Parker 2019-04-23 04:35:48 -05:00
parent e54056ceac
commit e1b031e6db
5 changed files with 67 additions and 51 deletions

View File

@ -99,7 +99,7 @@
</connections>
</barButtonItem>
</toolbarItems>
<navigationItem key="navigationItem" id="mOI-FS-AaM"/>
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="mOI-FS-AaM"/>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="actionBarButtonItem" destination="9Ut-5B-JKP" id="9bO-kz-cTz"/>
@ -149,16 +149,17 @@
</connections>
</barButtonItem>
<barButtonItem style="plain" systemItem="flexibleSpace" id="93y-8j-WBh"/>
<barButtonItem enabled="NO" title="First Unread" id="2v2-jD-C9k">
<barButtonItem title="First Unread" id="2v2-jD-C9k">
<connections>
<action selector="nextUnread:" destination="Kyk-vK-QRX" id="lwt-VU-zdW"/>
<action selector="firstUnread:" destination="Kyk-vK-QRX" id="d5y-x5-Qht"/>
</connections>
</barButtonItem>
</toolbarItems>
<navigationItem key="navigationItem" title="Timeline" largeTitleDisplayMode="never" id="wcC-1L-ug4"/>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="nextUnreadButton" destination="2v2-jD-C9k" id="9rf-5I-18f"/>
<outlet property="firstUnreadButton" destination="2v2-jD-C9k" id="8NP-Uc-3Fn"/>
<outlet property="markAllAsReadButton" destination="fTv-eX-72r" id="12S-lN-Sxa"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="nzm-Gf-Xce" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -244,6 +245,9 @@
</barButtonItem>
</navigationItem>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="markAllAsReadButton" destination="ddj-Ya-Wol" id="jjr-OK-4zl"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Rux-fX-hf1" sceneMemberID="firstResponder"/>
</objects>

View File

@ -26,14 +26,18 @@ class DetailViewController: UIViewController {
weak var navState: NavigationStateController?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationItem.largeTitleDisplayMode = .never
webView.navigationDelegate = self
markAsRead()
reloadUI()
updateUI()
reloadHTML()
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)
}
func markAsRead() {
@ -42,7 +46,7 @@ class DetailViewController: UIViewController {
}
}
func reloadUI() {
func updateUI() {
guard let article = navState?.currentArticle else {
nextUnreadBarButtonItem.isEnabled = false
@ -55,7 +59,7 @@ class DetailViewController: UIViewController {
return
}
nextUnreadBarButtonItem.isEnabled = navState?.isNextUnreadAvailable ?? false
nextUnreadBarButtonItem.isEnabled = navState?.isAnyUnreadAvailable ?? false
prevArticleBarButtonItem.isEnabled = navState?.isPrevArticleAvailable ?? false
nextArticleBarButtonItem.isEnabled = navState?.isNextArticleAvailable ?? false
@ -83,18 +87,24 @@ class DetailViewController: UIViewController {
}
// MARK: Notifications
@objc dynamic func unreadCountDidChange(_ notification: Notification) {
updateUI()
}
@objc func statusesDidChange(_ note: Notification) {
guard let articles = note.userInfo?[Account.UserInfoKey.articles] as? Set<Article> else {
return
}
if articles.count == 1 && articles.first?.articleID == navState?.currentArticle?.articleID {
reloadUI()
updateUI()
}
}
@objc func articleSelectionDidChange(_ note: Notification) {
markAsRead()
reloadUI()
updateUI()
reloadHTML()
}

View File

@ -14,6 +14,8 @@ import RSTree
class MasterViewController: UITableViewController, UndoableCommandRunner {
@IBOutlet weak var markAllAsReadButton: UIBarButtonItem!
var undoableCommands = [UndoableCommand]()
let navState = NavigationStateController()
@ -29,18 +31,20 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
tableView.register(MasterTableViewSectionHeader.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
NotificationCenter.default.addObserver(self, selector: #selector(backingStoresDidRebuild(_:)), name: .BackingStoresDidRebuild, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
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)
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
updateUI()
}
override func viewWillAppear(_ animated: Bool) {
@ -89,6 +93,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
}
configureUnreadCountForCellsForRepresentedObject(representedObject as AnyObject)
updateUI()
}
@ -138,13 +143,11 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
}
@objc func masterSelectionDidChange(_ note: Notification) {
if let indexPath = navState.currentMasterIndexPath {
if tableView.indexPathForSelectedRow != indexPath {
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
}
}
}
// MARK: Table View
@ -625,6 +628,10 @@ private extension MasterViewController {
AccountManager.shared.refreshAll()
}
func updateUI() {
markAllAsReadButton.isEnabled = navState.isAnyUnreadAvailable
}
func configureCellsForRepresentedObject(_ representedObject: AnyObject) {
applyToCellsForRepresentedObject(representedObject, configure)

View File

@ -114,6 +114,15 @@ class NavigationStateController {
return IndexPath(row: indexPath.row + 1, section: indexPath.section)
}
var firstUnreadArticleIndexPath: IndexPath? {
for (row, article) in articles.enumerated() {
if !article.status.read {
return IndexPath(row: row, section: 0)
}
}
return nil
}
var currentArticle: Article? {
if let indexPath = currentArticleIndexPath {
return articles[indexPath.row]
@ -145,7 +154,14 @@ class NavigationStateController {
}
}
var isNextUnreadAvailable: Bool {
var isTimelineUnreadAvailable: Bool {
if let unreadProvider = timelineFetcher as? UnreadCountProvider {
return unreadProvider.unreadCount > 0
}
return false
}
var isAnyUnreadAvailable: Bool {
return appDelegate.unreadCount > 0
}

View File

@ -20,7 +20,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
return navState?.showFeedNames ?? false ? rowHeightWithFeedName : rowHeightWithoutFeedName
}
@IBOutlet weak var nextUnreadButton: UIBarButtonItem!
@IBOutlet weak var markAllAsReadButton: UIBarButtonItem!
@IBOutlet weak var firstUnreadButton: UIBarButtonItem!
weak var navState: NavigationStateController?
var undoableCommands = [UndoableCommand]()
@ -34,6 +35,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
super.viewDidLoad()
updateRowHeights()
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(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
@ -49,11 +51,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
if let splitViewController = splitViewController {
splitViewController.delegate = self
changeToDisplayMode(splitViewController.displayMode)
}
resetUI()
}
@ -107,8 +104,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
}
@IBAction func nextUnread(_ sender: Any) {
navState?.selectNextUnread()
@IBAction func firstUnread(_ sender: Any) {
if let indexPath = navState?.firstUnreadArticleIndexPath {
tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
}
}
// MARK: - Table view
@ -193,6 +192,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
}
}
@objc dynamic func unreadCountDidChange(_ notification: Notification) {
updateUI()
}
@objc func statusesDidChange(_ note: Notification) {
guard let articles = note.userInfo?[Account.UserInfoKey.articles] as? Set<Article> else {
@ -276,7 +279,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
}
}
reloadUI()
updateUI()
}
@ -341,14 +344,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
}
extension MasterTimelineViewController: UISplitViewControllerDelegate {
func splitViewController(_ svc: UISplitViewController, willChangeTo displayMode: UISplitViewController.DisplayMode) {
changeToDisplayMode(displayMode)
}
}
// MARK: Private
private extension MasterTimelineViewController {
@ -357,20 +352,6 @@ private extension MasterTimelineViewController {
AccountManager.shared.refreshAll()
}
func changeToDisplayMode(_ displayMode: UISplitViewController.DisplayMode) {
if UIDevice.current.userInterfaceIdiom == .pad && displayMode == .allVisible {
nextUnreadButton.isEnabled = false
nextUnreadButton.title = ""
} else {
nextUnreadButton.isEnabled = false
nextUnreadButton.title = NSLocalizedString("First Unread", comment: "First Unread")
}
reloadUI()
}
func resetUI() {
updateTableViewRowHeight()
@ -380,15 +361,13 @@ private extension MasterTimelineViewController {
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
}
reloadUI()
updateUI()
}
func reloadUI() {
// Since there is no hidden property on a bar button item, we just hide its title
if !(nextUnreadButton.title?.isEmpty ?? true) {
nextUnreadButton.isEnabled = navState?.isNextUnreadAvailable ?? false
}
func updateUI() {
markAllAsReadButton.isEnabled = navState?.isTimelineUnreadAvailable ?? false
firstUnreadButton.isEnabled = navState?.isTimelineUnreadAvailable ?? false
}
func configureTimelineCell(_ cell: MasterTimelineTableViewCell, article: Article) {