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()