diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift
index 3fcf1e3d2..32ba79313 100644
--- a/Frameworks/Account/Feed.swift
+++ b/Frameworks/Account/Feed.swift
@@ -9,6 +9,14 @@
import Foundation
import RSCore
+public enum ReadFilter {
+ case read
+ case all
+ case none
+}
+
public protocol Feed: FeedIdentifiable, ArticleFetcher, DisplayNameProvider, UnreadCountProvider {
+ var defaultReadFilter: ReadFilter { get }
+
}
diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift
index cf6f1df57..fd3ab44ec 100644
--- a/Frameworks/Account/Folder.swift
+++ b/Frameworks/Account/Folder.swift
@@ -12,6 +12,10 @@ import RSCore
public final class Folder: Feed, Renamable, Container, Hashable {
+ public var defaultReadFilter: ReadFilter {
+ return .read
+ }
+
public var feedID: FeedIdentifier? {
guard let accountID = account?.accountID else {
assertionFailure("Expected feed.account, but got nil.")
diff --git a/Frameworks/Account/WebFeed.swift b/Frameworks/Account/WebFeed.swift
index 929701123..2bcc1dc5e 100644
--- a/Frameworks/Account/WebFeed.swift
+++ b/Frameworks/Account/WebFeed.swift
@@ -13,6 +13,10 @@ import Articles
public final class WebFeed: Feed, Renamable, Hashable {
+ public var defaultReadFilter: ReadFilter {
+ return .all
+ }
+
public var feedID: FeedIdentifier? {
guard let accountID = account?.accountID else {
assertionFailure("Expected feed.account, but got nil.")
diff --git a/Shared/SmartFeeds/SmartFeed.swift b/Shared/SmartFeeds/SmartFeed.swift
index 0419c47a1..b3e4a59fb 100644
--- a/Shared/SmartFeeds/SmartFeed.swift
+++ b/Shared/SmartFeeds/SmartFeed.swift
@@ -13,6 +13,10 @@ import Account
final class SmartFeed: PseudoFeed {
+ public var defaultReadFilter: ReadFilter {
+ return .all
+ }
+
var feedID: FeedIdentifier? {
delegate.feedID
}
diff --git a/Shared/SmartFeeds/UnreadFeed.swift b/Shared/SmartFeeds/UnreadFeed.swift
index f41793988..8238425a2 100644
--- a/Shared/SmartFeeds/UnreadFeed.swift
+++ b/Shared/SmartFeeds/UnreadFeed.swift
@@ -19,6 +19,10 @@ import Articles
final class UnreadFeed: PseudoFeed {
+ public var defaultReadFilter: ReadFilter {
+ return .none
+ }
+
var feedID: FeedIdentifier? {
return FeedIdentifier.smartFeed(String(describing: UnreadFeed.self))
}
diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard
index 194e60b7b..61184ea2b 100644
--- a/iOS/Base.lproj/Main.storyboard
+++ b/iOS/Base.lproj/Main.storyboard
@@ -167,10 +167,17 @@
-
+
+
+
+
+
+
+
+
diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift
index e1ec464b8..762b065ff 100644
--- a/iOS/MasterTimeline/MasterTimelineViewController.swift
+++ b/iOS/MasterTimeline/MasterTimelineViewController.swift
@@ -17,6 +17,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
private var numberOfTextLines = 0
private var iconSize = IconSize.medium
+ @IBOutlet weak var filterButton: UIBarButtonItem!
@IBOutlet weak var markAllAsReadButton: UIBarButtonItem!
@IBOutlet weak var firstUnreadButton: UIBarButtonItem!
@@ -87,7 +88,19 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
}
// MARK: Actions
-
+ @IBAction func toggleFilter(_ sender: Any) {
+ switch coordinator.articleReadFilter {
+ case .all:
+ filterButton.image = AppAssets.filterActiveImage
+ coordinator.hideUnreadArticles()
+ case .read:
+ filterButton.image = AppAssets.filterInactiveImage
+ coordinator.showAllArticles()
+ default:
+ break
+ }
+ }
+
@IBAction func markAllAsRead(_ sender: Any) {
if coordinator.displayUndoAvailableTip {
let alertController = UndoAvailableAlertController.alert { [weak self] _ in
@@ -466,7 +479,6 @@ extension MasterTimelineViewController: UISearchBarDelegate {
private extension MasterTimelineViewController {
func resetUI() {
- title = coordinator.timelineFeed?.nameForDisplay
if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView {
self.titleView = titleView
@@ -484,6 +496,16 @@ private extension MasterTimelineViewController {
navigationItem.titleView = titleView
}
+ switch coordinator.articleReadFilter {
+ case .all:
+ filterButton.isHidden = false
+ filterButton.image = AppAssets.filterInactiveImage
+ case .read:
+ filterButton.isHidden = false
+ filterButton.image = AppAssets.filterActiveImage
+ default:
+ filterButton.isHidden = true
+ }
tableView.selectRow(at: nil, animated: false, scrollPosition: .top)
if dataSource.snapshot().itemIdentifiers(inSection: 0).count > 0 {
diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift
index 91c7fcf9f..a82a121e1 100644
--- a/iOS/SceneCoordinator.swift
+++ b/iOS/SceneCoordinator.swift
@@ -116,6 +116,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
return treeControllerDelegate.isUnreadFiltered
}
+ var articleReadFilter: ReadFilter = .all
+
var rootNode: Node {
return treeController.rootNode
}
@@ -469,6 +471,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
treeControllerDelegate.isUnreadFiltered = true
rebuildBackingStores()
}
+
+ func showAllArticles() {
+ articleReadFilter = .all
+ }
+
+ func hideUnreadArticles() {
+ articleReadFilter = .read
+ }
func expand(_ node: Node) {
node.isExpanded = true
@@ -1129,6 +1139,7 @@ private extension SceneCoordinator {
func setTimelineFeed(_ feed: Feed?, completion: (() -> Void)? = nil) {
timelineFeed = feed
timelineMiddleIndexPath = nil
+ articleReadFilter = feed?.defaultReadFilter ?? .all
fetchAndReplaceArticlesAsync {
self.masterTimelineViewController?.reinitializeArticles()