Merge pull request #6 from brentsimmons/master

merge from upstream
This commit is contained in:
Olof Hellman 2019-07-28 16:08:39 -07:00 committed by GitHub
commit a2bf47d9e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 84 deletions

View File

@ -381,12 +381,25 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
extension MainWindowController: SidebarDelegate { extension MainWindowController: SidebarDelegate {
func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?) { func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?) {
// TODO: if searching, cancel search // Dont update the timeline if it already has those objects.
let representedObjectsAreTheSame = timelineContainerViewController?.regularTimelineViewControllerHasRepresentedObjects(selectedObjects) ?? false
if !representedObjectsAreTheSame {
timelineContainerViewController?.setRepresentedObjects(selectedObjects, mode: .regular) timelineContainerViewController?.setRepresentedObjects(selectedObjects, mode: .regular)
forceSearchToEnd() forceSearchToEnd()
}
updateWindowTitle() updateWindowTitle()
NotificationCenter.default.post(name: .InspectableObjectsDidChange, object: nil) NotificationCenter.default.post(name: .InspectableObjectsDidChange, object: nil)
} }
func unreadCount(for representedObject: AnyObject) -> Int {
guard let timelineViewController = regularTimelineViewController else {
return 0
}
guard timelineViewController.representsThisObjectOnly(representedObject) else {
return 0
}
return timelineViewController.unreadCount
}
} }
// MARK: - TimelineContainerViewControllerDelegate // MARK: - TimelineContainerViewControllerDelegate
@ -545,6 +558,10 @@ private extension MainWindowController {
return timelineContainerViewController?.currentTimelineViewController return timelineContainerViewController?.currentTimelineViewController
} }
var regularTimelineViewController: TimelineViewController? {
return timelineContainerViewController?.regularTimelineViewController
}
var sidebarSplitViewItem: NSSplitViewItem? { var sidebarSplitViewItem: NSSplitViewItem? {
return splitViewController?.splitViewItems[0] return splitViewController?.splitViewItems[0]
} }

View File

@ -14,6 +14,7 @@ import RSCore
protocol SidebarDelegate: class { protocol SidebarDelegate: class {
func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?) func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?)
func unreadCount(for: AnyObject) -> Int
} }
@objc class SidebarViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuDelegate, UndoableCommandRunner { @objc class SidebarViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuDelegate, UndoableCommandRunner {
@ -43,7 +44,6 @@ protocol SidebarDelegate: class {
// MARK: - NSViewController // MARK: - NSViewController
override func viewDidLoad() { override func viewDidLoad() {
sidebarCellAppearance = SidebarCellAppearance(fontSize: AppDefaults.sidebarFontSize) sidebarCellAppearance = SidebarCellAppearance(fontSize: AppDefaults.sidebarFontSize)
outlineView.dataSource = dataSource outlineView.dataSource = dataSource
@ -60,7 +60,6 @@ protocol SidebarDelegate: class {
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidRequestSidebarSelection(_:)), name: .UserDidRequestSidebarSelection, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidRequestSidebarSelection(_:)), name: .UserDidRequestSidebarSelection, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(calendarDayChanged(_:)), name: .NSCalendarDayChanged, object: nil)
outlineView.reloadData() outlineView.reloadData()
@ -78,30 +77,9 @@ protocol SidebarDelegate: class {
} }
} }
// MARK: State Restoration
// private static let stateRestorationSelectedRowIndexes = "selectedRowIndexes"
//
// override func encodeRestorableState(with coder: NSCoder) {
//
// super.encodeRestorableState(with: coder)
//
// coder.encode(outlineView.selectedRowIndexes, forKey: SidebarViewController.stateRestorationSelectedRowIndexes)
// }
//
// override func restoreState(with coder: NSCoder) {
//
// super.restoreState(with: coder)
//
// if let restoredRowIndexes = coder.decodeObject(of: [NSIndexSet.self], forKey: SidebarViewController.stateRestorationSelectedRowIndexes) as? IndexSet {
// outlineView.selectRowIndexes(restoredRowIndexes, byExtendingSelection: false)
// }
// }
// MARK: - Notifications // MARK: - Notifications
@objc func unreadCountDidChange(_ note: Notification) { @objc func unreadCountDidChange(_ note: Notification) {
guard let representedObject = note.object else { guard let representedObject = note.object else {
return return
} }
@ -125,7 +103,6 @@ protocol SidebarDelegate: class {
} }
@objc func userDidAddFeed(_ notification: Notification) { @objc func userDidAddFeed(_ notification: Notification) {
guard let feed = notification.userInfo?[UserInfoKey.feed] else { guard let feed = notification.userInfo?[UserInfoKey.feed] else {
return return
} }
@ -133,12 +110,10 @@ protocol SidebarDelegate: class {
} }
@objc func faviconDidBecomeAvailable(_ note: Notification) { @objc func faviconDidBecomeAvailable(_ note: Notification) {
applyToAvailableCells(configureFavicon) applyToAvailableCells(configureFavicon)
} }
@objc func feedSettingDidChange(_ note: Notification) { @objc func feedSettingDidChange(_ note: Notification) {
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else { guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
return return
} }
@ -148,7 +123,6 @@ protocol SidebarDelegate: class {
} }
@objc func displayNameDidChange(_ note: Notification) { @objc func displayNameDidChange(_ note: Notification) {
guard let object = note.object else { guard let object = note.object else {
return return
} }
@ -159,23 +133,15 @@ protocol SidebarDelegate: class {
} }
@objc func userDidRequestSidebarSelection(_ note: Notification) { @objc func userDidRequestSidebarSelection(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] else { guard let feed = note.userInfo?[UserInfoKey.feed] else {
return return
} }
revealAndSelectRepresentedObject(feed as AnyObject) revealAndSelectRepresentedObject(feed as AnyObject)
} }
@objc func calendarDayChanged(_ note: Notification) {
DispatchQueue.main.async {
SmartFeedsController.shared.todayFeed.fetchUnreadCounts()
}
}
// MARK: - Actions // MARK: - Actions
@IBAction func delete(_ sender: AnyObject?) { @IBAction func delete(_ sender: AnyObject?) {
if outlineView.selectionIsEmpty { if outlineView.selectionIsEmpty {
return return
} }
@ -183,7 +149,6 @@ protocol SidebarDelegate: class {
} }
@IBAction func openInBrowser(_ sender: Any?) { @IBAction func openInBrowser(_ sender: Any?) {
guard let feed = singleSelectedFeed, let homePageURL = feed.homePageURL else { guard let feed = singleSelectedFeed, let homePageURL = feed.homePageURL else {
return return
} }
@ -191,29 +156,24 @@ protocol SidebarDelegate: class {
} }
@IBAction func gotoToday(_ sender: Any?) { @IBAction func gotoToday(_ sender: Any?) {
outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.todayFeed, treeController) outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.todayFeed, treeController)
} }
@IBAction func gotoAllUnread(_ sender: Any?) { @IBAction func gotoAllUnread(_ sender: Any?) {
outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.unreadFeed, treeController) outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.unreadFeed, treeController)
} }
@IBAction func gotoStarred(_ sender: Any?) { @IBAction func gotoStarred(_ sender: Any?) {
outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.starredFeed, treeController) outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.starredFeed, treeController)
} }
@IBAction func copy(_ sender: Any?) { @IBAction func copy(_ sender: Any?) {
NSPasteboard.general.copyObjects(selectedObjects) NSPasteboard.general.copyObjects(selectedObjects)
} }
// MARK: - Navigation // MARK: - Navigation
func canGoToNextUnread() -> Bool { func canGoToNextUnread() -> Bool {
if let _ = nextSelectableRowWithUnreadArticle() { if let _ = nextSelectableRowWithUnreadArticle() {
return true return true
} }
@ -221,7 +181,6 @@ protocol SidebarDelegate: class {
} }
func goToNextUnread() { func goToNextUnread() {
guard let row = nextSelectableRowWithUnreadArticle() else { guard let row = nextSelectableRowWithUnreadArticle() else {
assertionFailure("goToNextUnread called before checking if there is a next unread.") assertionFailure("goToNextUnread called before checking if there is a next unread.")
return return
@ -230,26 +189,19 @@ protocol SidebarDelegate: class {
NSCursor.setHiddenUntilMouseMoves(true) NSCursor.setHiddenUntilMouseMoves(true)
outlineView.selectRowIndexes(IndexSet([row]), byExtendingSelection: false) outlineView.selectRowIndexes(IndexSet([row]), byExtendingSelection: false)
outlineView.scrollTo(row: row) outlineView.scrollTo(row: row)
} }
func focus() { func focus() {
outlineView.window?.makeFirstResponderUnlessDescendantIsFirstResponder(outlineView)
guard let window = outlineView.window else {
return
}
window.makeFirstResponderUnlessDescendantIsFirstResponder(outlineView)
} }
// MARK: - Contextual Menu // MARK: - Contextual Menu
func contextualMenuForSelectedObjects() -> NSMenu? { func contextualMenuForSelectedObjects() -> NSMenu? {
return menu(for: selectedObjects) return menu(for: selectedObjects)
} }
func contextualMenuForClickedRows() -> NSMenu? { func contextualMenuForClickedRows() -> NSMenu? {
let row = outlineView.clickedRow let row = outlineView.clickedRow
guard row != -1, let node = nodeForRow(row) else { guard row != -1, let node = nodeForRow(row) else {
return nil return nil
@ -264,7 +216,7 @@ protocol SidebarDelegate: class {
return menu(for: [object]) return menu(for: [object])
} }
// MARK: NSMenuDelegate // MARK: - NSMenuDelegate
public func menuNeedsUpdate(_ menu: NSMenu) { public func menuNeedsUpdate(_ menu: NSMenu) {
menu.removeAllItems() menu.removeAllItems()
@ -278,7 +230,6 @@ protocol SidebarDelegate: class {
// MARK: - NSOutlineViewDelegate // MARK: - NSOutlineViewDelegate
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let node = item as! Node let node = item as! Node
if node.isGroupItem { if node.isGroupItem {
@ -294,13 +245,11 @@ protocol SidebarDelegate: class {
} }
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool { func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
let node = item as! Node let node = item as! Node
return node.isGroupItem return node.isGroupItem
} }
func outlineView(_ outlineView: NSOutlineView, selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet) -> IndexSet { func outlineView(_ outlineView: NSOutlineView, selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet) -> IndexSet {
// Dont allow selecting group items. // Dont allow selecting group items.
// If any index in IndexSet contains a group item, // If any index in IndexSet contains a group item,
// return the current selection (not a modified version of the proposed selection). // return the current selection (not a modified version of the proposed selection).
@ -315,19 +264,16 @@ protocol SidebarDelegate: class {
} }
func outlineView(_ outlineView: NSOutlineView, shouldSelectItem item: Any) -> Bool { func outlineView(_ outlineView: NSOutlineView, shouldSelectItem item: Any) -> Bool {
return !self.outlineView(outlineView, isGroupItem: item) return !self.outlineView(outlineView, isGroupItem: item)
} }
func outlineViewSelectionDidChange(_ notification: Notification) { func outlineViewSelectionDidChange(_ notification: Notification) {
selectionDidChange(selectedObjects.isEmpty ? nil : selectedObjects) selectionDidChange(selectedObjects.isEmpty ? nil : selectedObjects)
// self.invalidateRestorableState()
} }
//MARK: - Node Manipulation //MARK: - Node Manipulation
func deleteNodes(_ nodes: [Node]) { func deleteNodes(_ nodes: [Node]) {
let nodesToDelete = treeController.normalizedSelectedNodes(nodes) let nodesToDelete = treeController.normalizedSelectedNodes(nodes)
guard let undoManager = undoManager, let deleteCommand = DeleteCommand(nodesToDelete: nodesToDelete, treeController: treeController, undoManager: undoManager, errorHandler: ErrorHandler.present) else { guard let undoManager = undoManager, let deleteCommand = DeleteCommand(nodesToDelete: nodesToDelete, treeController: treeController, undoManager: undoManager, errorHandler: ErrorHandler.present) else {
@ -374,7 +320,6 @@ protocol SidebarDelegate: class {
extension SidebarViewController: NSUserInterfaceValidations { extension SidebarViewController: NSUserInterfaceValidations {
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
if item.action == #selector(copy(_:)) { if item.action == #selector(copy(_:)) {
return NSPasteboard.general.canCopyAtLeastOneObject(selectedObjects) return NSPasteboard.general.canCopyAtLeastOneObject(selectedObjects)
} }
@ -412,12 +357,12 @@ private extension SidebarViewController {
} }
func rebuildTreeAndReloadDataIfNeeded() { func rebuildTreeAndReloadDataIfNeeded() {
if !animatingChanges && !BatchUpdate.shared.isPerforming { if !animatingChanges && !BatchUpdate.shared.isPerforming {
treeController.rebuild() if treeController.rebuild() {
outlineView.reloadData() outlineView.reloadData()
} }
} }
}
func restoreSelection(to nodes: [Node], sendNotificationIfChanged: Bool) { func restoreSelection(to nodes: [Node], sendNotificationIfChanged: Bool) {
if selectedNodes == nodes { // Nothing to do? if selectedNodes == nodes { // Nothing to do?
@ -444,7 +389,6 @@ private extension SidebarViewController {
} }
func updateUnreadCounts(for objects: [AnyObject]) { func updateUnreadCounts(for objects: [AnyObject]) {
// On selection, update unread counts for folders and feeds. // On selection, update unread counts for folders and feeds.
// For feeds, actually fetch from database. // For feeds, actually fetch from database.
@ -459,7 +403,6 @@ private extension SidebarViewController {
} }
func nodeForItem(_ item: AnyObject?) -> Node { func nodeForItem(_ item: AnyObject?) -> Node {
if item == nil { if item == nil {
return treeController.rootNode return treeController.rootNode
} }
@ -467,7 +410,6 @@ private extension SidebarViewController {
} }
func nodeForRow(_ row: Int) -> Node? { func nodeForRow(_ row: Int) -> Node? {
if row < 0 || row >= outlineView.numberOfRows { if row < 0 || row >= outlineView.numberOfRows {
return nil return nil
} }
@ -479,7 +421,6 @@ private extension SidebarViewController {
} }
func rowHasAtLeastOneUnreadArticle(_ row: Int) -> Bool { func rowHasAtLeastOneUnreadArticle(_ row: Int) -> Bool {
if let oneNode = nodeForRow(row) { if let oneNode = nodeForRow(row) {
if let unreadCountProvider = oneNode.representedObject as? UnreadCountProvider { if let unreadCountProvider = oneNode.representedObject as? UnreadCountProvider {
if unreadCountProvider.unreadCount > 0 { if unreadCountProvider.unreadCount > 0 {
@ -491,7 +432,6 @@ private extension SidebarViewController {
} }
func rowIsGroupItem(_ row: Int) -> Bool { func rowIsGroupItem(_ row: Int) -> Bool {
if let node = nodeForRow(row), outlineView.isGroupItem(node) { if let node = nodeForRow(row), outlineView.isGroupItem(node) {
return true return true
} }
@ -499,7 +439,6 @@ private extension SidebarViewController {
} }
func nextSelectableRowWithUnreadArticle() -> Int? { func nextSelectableRowWithUnreadArticle() -> Int? {
// Skip group items, because they should never be selected. // Skip group items, because they should never be selected.
let selectedRow = outlineView.selectedRow let selectedRow = outlineView.selectedRow
@ -533,12 +472,10 @@ private extension SidebarViewController {
} }
func configureUnreadCount(_ cell: SidebarCell, _ node: Node) { func configureUnreadCount(_ cell: SidebarCell, _ node: Node) {
cell.unreadCount = unreadCountFor(node) cell.unreadCount = unreadCountFor(node)
} }
func configureFavicon(_ cell: SidebarCell, _ node: Node) { func configureFavicon(_ cell: SidebarCell, _ node: Node) {
cell.image = imageFor(node) cell.image = imageFor(node)
} }
@ -547,7 +484,6 @@ private extension SidebarViewController {
} }
func imageFor(_ node: Node) -> NSImage? { func imageFor(_ node: Node) -> NSImage? {
if let smallIconProvider = node.representedObject as? SmallIconProvider { if let smallIconProvider = node.representedObject as? SmallIconProvider {
return smallIconProvider.smallIcon return smallIconProvider.smallIcon
} }
@ -555,7 +491,6 @@ private extension SidebarViewController {
} }
func nameFor(_ node: Node) -> String { func nameFor(_ node: Node) -> String {
if let displayNameProvider = node.representedObject as? DisplayNameProvider { if let displayNameProvider = node.representedObject as? DisplayNameProvider {
return displayNameProvider.nameForDisplay return displayNameProvider.nameForDisplay
} }
@ -563,6 +498,13 @@ private extension SidebarViewController {
} }
func unreadCountFor(_ node: Node) -> Int { func unreadCountFor(_ node: Node) -> Int {
// If this node is the one and only selection,
// then the unread count comes from the timeline.
// This ensures that any transients in the timeline
// are accounted for in the unread count.
if selectedNodes.count == 1 && node === selectedNodes.first! {
return delegate?.unreadCount(for: node.representedObject) ?? 0
}
if let unreadCountProvider = node.representedObject as? UnreadCountProvider { if let unreadCountProvider = node.representedObject as? UnreadCountProvider {
return unreadCountProvider.unreadCount return unreadCountProvider.unreadCount
@ -571,14 +513,11 @@ private extension SidebarViewController {
} }
func cellForRowView(_ rowView: NSTableRowView) -> SidebarCell? { func cellForRowView(_ rowView: NSTableRowView) -> SidebarCell? {
return rowView.view(atColumn: 0) as? SidebarCell return rowView.view(atColumn: 0) as? SidebarCell
} }
func applyToAvailableCells(_ callback: (SidebarCell, Node) -> Void) { func applyToAvailableCells(_ callback: (SidebarCell, Node) -> Void) {
outlineView.enumerateAvailableRowViews { (rowView: NSTableRowView, row: Int) -> Void in outlineView.enumerateAvailableRowViews { (rowView: NSTableRowView, row: Int) -> Void in
guard let cell = cellForRowView(rowView), let node = nodeForRow(row) else { guard let cell = cellForRowView(rowView), let node = nodeForRow(row) else {
return return
} }
@ -595,23 +534,19 @@ private extension SidebarViewController {
} }
func configureCellsForRepresentedObject(_ representedObject: AnyObject) { func configureCellsForRepresentedObject(_ representedObject: AnyObject) {
applyToCellsForRepresentedObject(representedObject, configure) applyToCellsForRepresentedObject(representedObject, configure)
} }
func configureUnreadCountForCellsForRepresentedObject(_ representedObject: AnyObject) { func configureUnreadCountForCellsForRepresentedObject(_ representedObject: AnyObject) {
applyToCellsForRepresentedObject(representedObject, configureUnreadCount) applyToCellsForRepresentedObject(representedObject, configureUnreadCount)
} }
@discardableResult @discardableResult
func revealAndSelectRepresentedObject(_ representedObject: AnyObject) -> Bool { func revealAndSelectRepresentedObject(_ representedObject: AnyObject) -> Bool {
return outlineView.revealAndSelectRepresentedObject(representedObject, treeController) return outlineView.revealAndSelectRepresentedObject(representedObject, treeController)
} }
} }
private extension Node { private extension Node {
func representsSidebarObject(_ object: AnyObject) -> Bool { func representsSidebarObject(_ object: AnyObject) -> Bool {

View File

@ -30,7 +30,7 @@ final class TimelineContainerViewController: NSViewController {
weak var delegate: TimelineContainerViewControllerDelegate? weak var delegate: TimelineContainerViewControllerDelegate?
private lazy var regularTimelineViewController = { lazy var regularTimelineViewController = {
return TimelineViewController(delegate: self) return TimelineViewController(delegate: self)
}() }()
private lazy var searchTimelineViewController: TimelineViewController = { private lazy var searchTimelineViewController: TimelineViewController = {
@ -54,6 +54,31 @@ final class TimelineContainerViewController: NSViewController {
func showTimeline(for mode: TimelineSourceMode) { func showTimeline(for mode: TimelineSourceMode) {
currentTimelineViewController = timelineViewController(for: mode) currentTimelineViewController = timelineViewController(for: mode)
} }
func regularTimelineViewControllerHasRepresentedObjects(_ representedObjects: [AnyObject]?) -> Bool {
// Use this to find out if the regular timeline view already has the specified representedObjects.
// This is used in determining whether a search should end.
// The sidebar may think that the selection has changed, and therefore search should end
// but it could be that the regular timeline already has these representedObjects,
// and therefore the selection hasnt actually changed,
// and therefore search shouldnt end.
// https://github.com/brentsimmons/NetNewsWire/issues/791
if representedObjects == nil && regularTimelineViewController.representedObjects == nil {
return true
}
guard let currentObjects = regularTimelineViewController.representedObjects, let representedObjects = representedObjects else {
return false
}
if currentObjects.count != representedObjects.count {
return false
}
for object in representedObjects {
guard let _ = currentObjects.firstIndex(where: { $0 === object } ) else {
return false
}
}
return true
}
} }
extension TimelineContainerViewController: TimelineDelegate { extension TimelineContainerViewController: TimelineDelegate {

View File

@ -19,14 +19,14 @@ protocol TimelineDelegate: class {
func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?) func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?)
} }
final class TimelineViewController: NSViewController, UndoableCommandRunner { final class TimelineViewController: NSViewController, UndoableCommandRunner, UnreadCountProvider {
@IBOutlet var tableView: TimelineTableView! @IBOutlet var tableView: TimelineTableView!
var representedObjects: [AnyObject]? { var representedObjects: [AnyObject]? {
didSet { didSet {
if !representedObjectArraysAreEqual(oldValue, representedObjects) { if !representedObjectArraysAreEqual(oldValue, representedObjects) {
unreadCount = 0
if let representedObjects = representedObjects { if let representedObjects = representedObjects {
if representedObjects.count == 1 && representedObjects.first is Feed { if representedObjects.count == 1 && representedObjects.first is Feed {
showFeedNames = false showFeedNames = false
@ -76,11 +76,21 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner {
// Just reload visible cells in this case: dont call reloadData. // Just reload visible cells in this case: dont call reloadData.
articleRowMap = [String: Int]() articleRowMap = [String: Int]()
reloadVisibleCells() reloadVisibleCells()
updateUnreadCount()
return return
} }
updateShowAvatars() updateShowAvatars()
articleRowMap = [String: Int]() articleRowMap = [String: Int]()
tableView.reloadData() tableView.reloadData()
updateUnreadCount()
}
}
var unreadCount: Int = 0 {
didSet {
if unreadCount != oldValue {
postUnreadCountDidChangeNotification()
}
} }
} }
@ -221,6 +231,16 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner {
return selectedArticles.canMarkAllAsRead() return selectedArticles.canMarkAllAsRead()
} }
func representsThisObjectOnly(_ object: AnyObject) -> Bool {
guard let representedObjects = representedObjects else {
return false
}
if representedObjects.count != 1 {
return false
}
return representedObjects.first! === object
}
// MARK: - Actions // MARK: - Actions
@objc func openArticleInBrowser(_ sender: Any?) { @objc func openArticleInBrowser(_ sender: Any?) {
@ -448,6 +468,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner {
return return
} }
reloadVisibleCells(for: articles) reloadVisibleCells(for: articles)
updateUnreadCount()
} }
@objc func feedIconDidBecomeAvailable(_ note: Notification) { @objc func feedIconDidBecomeAvailable(_ note: Notification) {
@ -810,6 +831,16 @@ private extension TimelineViewController {
} }
} }
func updateUnreadCount() {
var count = 0
for article in articles {
if !article.status.read {
count += 1
}
}
unreadCount = count
}
func queueReloadAvailableCells() { func queueReloadAvailableCells() {
CoalescingQueue.standard.add(self, #selector(reloadAvailableCells)) CoalescingQueue.standard.add(self, #selector(reloadAvailableCells))