From e1b031e6db2204d282ef644e3816f9d3500a757d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 23 Apr 2019 04:35:48 -0500 Subject: [PATCH] Make First Unread scroll to first unread in timeline. Update Mark As Read and other unread dependent UI respond to unread count changing. --- iOS/Base.lproj/Main.storyboard | 12 +++-- iOS/Detail/DetailViewController.swift | 22 +++++--- iOS/Master/MasterViewController.swift | 13 +++-- iOS/NavigationStateController.swift | 18 ++++++- .../MasterTimelineViewController.swift | 53 ++++++------------- 5 files changed, 67 insertions(+), 51 deletions(-) diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard index fa5928103..638e4b65e 100644 --- a/iOS/Base.lproj/Main.storyboard +++ b/iOS/Base.lproj/Main.storyboard @@ -99,7 +99,7 @@ - + @@ -149,16 +149,17 @@ - + - + - + + @@ -244,6 +245,9 @@ + + + diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 9bcde3608..e8101eef3 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -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
else { return } if articles.count == 1 && articles.first?.articleID == navState?.currentArticle?.articleID { - reloadUI() + updateUI() } } @objc func articleSelectionDidChange(_ note: Notification) { markAsRead() - reloadUI() + updateUI() reloadHTML() } diff --git a/iOS/Master/MasterViewController.swift b/iOS/Master/MasterViewController.swift index 0752bcaac..21447bbab 100644 --- a/iOS/Master/MasterViewController.swift +++ b/iOS/Master/MasterViewController.swift @@ -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) diff --git a/iOS/NavigationStateController.swift b/iOS/NavigationStateController.swift index b29853184..b13e8df32 100644 --- a/iOS/NavigationStateController.swift +++ b/iOS/NavigationStateController.swift @@ -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 } diff --git a/iOS/Timeline/MasterTimelineViewController.swift b/iOS/Timeline/MasterTimelineViewController.swift index 8415d85aa..7abd6e657 100644 --- a/iOS/Timeline/MasterTimelineViewController.swift +++ b/iOS/Timeline/MasterTimelineViewController.swift @@ -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
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) {