Add read filter toggle for articles. Issue #130.
This commit is contained in:
parent
7667dbf60e
commit
5ac14fb910
|
@ -336,6 +336,12 @@
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||||
<items>
|
<items>
|
||||||
|
<menuItem title="Hide Read Articles" id="b10-sA-Yzi">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleReadArticlesFilter:" target="Ady-hI-5gd" id="YhV-0F-jrM"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
<menuItem title="Sort Articles By" id="nLP-fa-KUi">
|
<menuItem title="Sort Articles By" id="nLP-fa-KUi">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Sort Articles By" id="OlJ-93-6OP">
|
<menu key="submenu" title="Sort Articles By" id="OlJ-93-6OP">
|
||||||
|
@ -365,7 +371,7 @@
|
||||||
<menuItem title="Hide Read Feeds" id="E9K-zV-nLv">
|
<menuItem title="Hide Read Feeds" id="E9K-zV-nLv">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleReadFilter:" target="Ady-hI-5gd" id="5pI-YT-xai"/>
|
<action selector="toggleReadFeedsFilter:" target="Ady-hI-5gd" id="5pI-YT-xai"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
|
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
|
||||||
|
|
|
@ -237,10 +237,19 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||||
return currentSearchField != nil
|
return currentSearchField != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.action == #selector(toggleReadFilter(_:)) {
|
if item.action == #selector(toggleReadFeedsFilter(_:)) {
|
||||||
(item as! NSMenuItem).state = sidebarViewController?.isReadFiltered ?? false ? .on : .off
|
(item as! NSMenuItem).state = sidebarViewController?.isReadFiltered ?? false ? .on : .off
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if item.action == #selector(toggleReadArticlesFilter(_:)) {
|
||||||
|
if let timelineContainer = timelineContainerViewController {
|
||||||
|
(item as! NSMenuItem).isEnabled = true
|
||||||
|
(item as! NSMenuItem).state = timelineContainer.isReadFiltered ? .on : .off
|
||||||
|
} else {
|
||||||
|
(item as! NSMenuItem).isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if item.action == #selector(toggleSidebar(_:)) {
|
if item.action == #selector(toggleSidebar(_:)) {
|
||||||
guard let splitViewItem = sidebarSplitViewItem else {
|
guard let splitViewItem = sidebarSplitViewItem else {
|
||||||
return false
|
return false
|
||||||
|
@ -443,9 +452,14 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||||
window?.makeFirstResponder(searchField)
|
window?.makeFirstResponder(searchField)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func toggleReadFilter(_ sender: Any?) {
|
@IBAction func toggleReadFeedsFilter(_ sender: Any?) {
|
||||||
sidebarViewController?.toggleReadFilter()
|
sidebarViewController?.toggleReadFilter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func toggleReadArticlesFilter(_ sender: Any?) {
|
||||||
|
timelineContainerViewController?.toggleReadFilter()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - SidebarDelegate
|
// MARK: - SidebarDelegate
|
||||||
|
|
|
@ -30,6 +30,10 @@ final class TimelineContainerViewController: NSViewController {
|
||||||
|
|
||||||
weak var delegate: TimelineContainerViewControllerDelegate?
|
weak var delegate: TimelineContainerViewControllerDelegate?
|
||||||
|
|
||||||
|
var isReadFiltered: Bool {
|
||||||
|
return regularTimelineViewController.isReadFiltered
|
||||||
|
}
|
||||||
|
|
||||||
lazy var regularTimelineViewController = {
|
lazy var regularTimelineViewController = {
|
||||||
return TimelineViewController(delegate: self)
|
return TimelineViewController(delegate: self)
|
||||||
}()
|
}()
|
||||||
|
@ -79,6 +83,11 @@ final class TimelineContainerViewController: NSViewController {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toggleReadFilter() {
|
||||||
|
regularTimelineViewController.toggleReadFilter()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TimelineContainerViewController: TimelineDelegate {
|
extension TimelineContainerViewController: TimelineDelegate {
|
||||||
|
|
|
@ -20,6 +20,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||||
|
|
||||||
@IBOutlet var tableView: TimelineTableView!
|
@IBOutlet var tableView: TimelineTableView!
|
||||||
|
|
||||||
|
private var articleReadFilterType: ReadFilterType?
|
||||||
|
var isReadFiltered: Bool {
|
||||||
|
return articleReadFilterType ?? .read != .none
|
||||||
|
}
|
||||||
|
|
||||||
var representedObjects: [AnyObject]? {
|
var representedObjects: [AnyObject]? {
|
||||||
didSet {
|
didSet {
|
||||||
if !representedObjectArraysAreEqual(oldValue, representedObjects) {
|
if !representedObjectArraysAreEqual(oldValue, representedObjects) {
|
||||||
|
@ -36,6 +41,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||||
showFeedNames = false
|
showFeedNames = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
determineReadFilterType()
|
||||||
selectionDidChange(nil)
|
selectionDidChange(nil)
|
||||||
if showsSearchResults {
|
if showsSearchResults {
|
||||||
fetchAndReplaceArticlesAsync()
|
fetchAndReplaceArticlesAsync()
|
||||||
|
@ -213,6 +219,19 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||||
return representedObjects.first! === object
|
return representedObjects.first! === object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toggleReadFilter() {
|
||||||
|
guard let filterType = articleReadFilterType else { return }
|
||||||
|
switch filterType {
|
||||||
|
case .alwaysRead:
|
||||||
|
break
|
||||||
|
case .read:
|
||||||
|
articleReadFilterType = ReadFilterType.none
|
||||||
|
case .none:
|
||||||
|
articleReadFilterType = ReadFilterType.read
|
||||||
|
}
|
||||||
|
fetchAndReplaceArticlesAsync()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
@objc func openArticleInBrowser(_ sender: Any?) {
|
@objc func openArticleInBrowser(_ sender: Any?) {
|
||||||
|
@ -945,6 +964,14 @@ private extension TimelineViewController {
|
||||||
|
|
||||||
// MARK: - Fetching Articles
|
// MARK: - Fetching Articles
|
||||||
|
|
||||||
|
func determineReadFilterType() {
|
||||||
|
if representedObjects?.count ?? 0 == 1, let feed = representedObjects?.first as? Feed {
|
||||||
|
articleReadFilterType = feed.defaultReadFilterType
|
||||||
|
} else {
|
||||||
|
articleReadFilterType = .read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fetchAndReplaceArticlesSync() {
|
func fetchAndReplaceArticlesSync() {
|
||||||
// To be called when the user has made a change of selection in the sidebar.
|
// To be called when the user has made a change of selection in the sidebar.
|
||||||
// It blocks the main thread, so that there’s no async delay,
|
// It blocks the main thread, so that there’s no async delay,
|
||||||
|
@ -990,7 +1017,11 @@ private extension TimelineViewController {
|
||||||
|
|
||||||
var fetchedArticles = Set<Article>()
|
var fetchedArticles = Set<Article>()
|
||||||
for articleFetcher in articleFetchers {
|
for articleFetcher in articleFetchers {
|
||||||
fetchedArticles.formUnion(articleFetcher.fetchArticles())
|
if articleReadFilterType != ReadFilterType.none {
|
||||||
|
fetchedArticles.formUnion(articleFetcher.fetchUnreadArticles())
|
||||||
|
} else {
|
||||||
|
fetchedArticles.formUnion(articleFetcher.fetchArticles())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fetchedArticles
|
return fetchedArticles
|
||||||
}
|
}
|
||||||
|
@ -1000,7 +1031,8 @@ private extension TimelineViewController {
|
||||||
// if it’s been superseded by a newer fetch, or the timeline was emptied, etc., it won’t get called.
|
// if it’s been superseded by a newer fetch, or the timeline was emptied, etc., it won’t get called.
|
||||||
precondition(Thread.isMainThread)
|
precondition(Thread.isMainThread)
|
||||||
cancelPendingAsyncFetches()
|
cancelPendingAsyncFetches()
|
||||||
let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: false, representedObjects: representedObjects) { [weak self] (articles, operation) in
|
let readFilter = articleReadFilterType != ReadFilterType.none
|
||||||
|
let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: readFilter, representedObjects: representedObjects) { [weak self] (articles, operation) in
|
||||||
precondition(Thread.isMainThread)
|
precondition(Thread.isMainThread)
|
||||||
guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else {
|
guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else {
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue