From 89e9a7b80e409633b503c76a44375f101a53015f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 21 Nov 2019 15:55:50 -0600 Subject: [PATCH] Add filter button show/hide unread feeds. Issue #1311 --- Shared/SmartFeeds/SmartFeedsController.swift | 3 +- .../Tree/WebFeedTreeControllerDelegate.swift | 56 ++++++++++++------- iOS/AppAssets.swift | 8 +++ iOS/Base.lproj/Main.storyboard | 9 +++ iOS/MasterFeed/MasterFeedViewController.swift | 11 ++++ iOS/SceneCoordinator.swift | 14 +++++ 6 files changed, 81 insertions(+), 20 deletions(-) diff --git a/Shared/SmartFeeds/SmartFeedsController.swift b/Shared/SmartFeeds/SmartFeedsController.swift index 37a424a17..683495606 100644 --- a/Shared/SmartFeeds/SmartFeedsController.swift +++ b/Shared/SmartFeeds/SmartFeedsController.swift @@ -8,13 +8,14 @@ import Foundation import RSCore +import Account final class SmartFeedsController: DisplayNameProvider { public static let shared = SmartFeedsController() let nameForDisplay = NSLocalizedString("Smart Feeds", comment: "Smart Feeds group title") - var smartFeeds = [AnyObject]() + var smartFeeds = [Feed]() let todayFeed = SmartFeed(delegate: TodayFeedDelegate()) let unreadFeed = UnreadFeed() let starredFeed = SmartFeed(delegate: StarredFeedDelegate()) diff --git a/Shared/Tree/WebFeedTreeControllerDelegate.swift b/Shared/Tree/WebFeedTreeControllerDelegate.swift index 62409cd14..6fa6fbc8c 100644 --- a/Shared/Tree/WebFeedTreeControllerDelegate.swift +++ b/Shared/Tree/WebFeedTreeControllerDelegate.swift @@ -13,8 +13,9 @@ import Account final class WebFeedTreeControllerDelegate: TreeControllerDelegate { + var isUnreadFiltered = false + func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? { - if node.isRoot { return childNodesForRootNode(node) } @@ -32,29 +33,47 @@ final class WebFeedTreeControllerDelegate: TreeControllerDelegate { private extension WebFeedTreeControllerDelegate { func childNodesForRootNode(_ rootNode: Node) -> [Node]? { + var topLevelNodes = [Node]() - // The top-level nodes are Smart Feeds and accounts. + // Check to see if we should show the SmartFeeds top level by checking the unreadFeed + if !(isUnreadFiltered && SmartFeedsController.shared.unreadFeed.unreadCount == 0) { + let smartFeedsNode = rootNode.existingOrNewChildNode(with: SmartFeedsController.shared) + smartFeedsNode.canHaveChildNodes = true + smartFeedsNode.isGroupItem = true + topLevelNodes.append(smartFeedsNode) + } - let smartFeedsNode = rootNode.existingOrNewChildNode(with: SmartFeedsController.shared) - smartFeedsNode.canHaveChildNodes = true - smartFeedsNode.isGroupItem = true - - return [smartFeedsNode] + sortedAccountNodes(rootNode) + topLevelNodes.append(contentsOf: sortedAccountNodes(rootNode)) + + return topLevelNodes } func childNodesForSmartFeeds(_ parentNode: Node) -> [Node] { - - return SmartFeedsController.shared.smartFeeds.map { parentNode.existingOrNewChildNode(with: $0) } + return SmartFeedsController.shared.smartFeeds.compactMap { (feed) -> Node? in + if isUnreadFiltered && feed.unreadCount == 0 { + return nil + } + return parentNode.existingOrNewChildNode(with: feed as AnyObject) + } } func childNodesForContainerNode(_ containerNode: Node) -> [Node]? { - let container = containerNode.representedObject as! Container var children = [AnyObject]() - children.append(contentsOf: Array(container.topLevelWebFeeds)) + + for webFeed in container.topLevelWebFeeds { + if !(isUnreadFiltered && webFeed.unreadCount == 0) { + children.append(webFeed) + } + } + if let folders = container.folders { - children.append(contentsOf: Array(folders)) + for folder in folders { + if !(isUnreadFiltered && folder.unreadCount == 0) { + children.append(folder) + } + } } var updatedChildNodes = [Node]() @@ -77,13 +96,14 @@ private extension WebFeedTreeControllerDelegate { } func createNode(representedObject: Any, parent: Node) -> Node? { - if let webFeed = representedObject as? WebFeed { return createNode(webFeed: webFeed, parent: parent) } + if let folder = representedObject as? Folder { return createNode(folder: folder, parent: parent) } + if let account = representedObject as? Account { return createNode(account: account, parent: parent) } @@ -92,19 +112,16 @@ private extension WebFeedTreeControllerDelegate { } func createNode(webFeed: WebFeed, parent: Node) -> Node { - return parent.createChildNode(webFeed) } func createNode(folder: Folder, parent: Node) -> Node { - let node = parent.createChildNode(folder) node.canHaveChildNodes = true return node } func createNode(account: Account, parent: Node) -> Node { - let node = parent.createChildNode(account) node.canHaveChildNodes = true node.isGroupItem = true @@ -112,8 +129,10 @@ private extension WebFeedTreeControllerDelegate { } func sortedAccountNodes(_ parent: Node) -> [Node] { - - let nodes = AccountManager.shared.sortedActiveAccounts.map { (account) -> Node in + let nodes = AccountManager.shared.sortedActiveAccounts.compactMap { (account) -> Node? in + if isUnreadFiltered && account.unreadCount == 0 { + return nil + } let accountNode = parent.existingOrNewChildNode(with: account) accountNode.canHaveChildNodes = true accountNode.isGroupItem = true @@ -123,7 +142,6 @@ private extension WebFeedTreeControllerDelegate { } func nodeInArrayRepresentingObject(_ nodes: [Node], _ representedObject: AnyObject) -> Node? { - for oneNode in nodes { if oneNode.representedObject === representedObject { return oneNode diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 20993af6f..081725a9a 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -89,6 +89,14 @@ struct AppAssets { return RSImage(named: "faviconTemplateImage")! }() + static var filterInactiveImage: UIImage = { + UIImage(systemName: "line.horizontal.3.decrease.circle")! + }() + + static var filterActiveImage: UIImage = { + UIImage(systemName: "line.horizontal.3.decrease.circle.fill")! + }() + static var fullScreenBackgroundColor: UIColor = { return UIColor(named: "fullScreenBackgroundColor")! }() diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard index dab0d6343..194e60b7b 100644 --- a/iOS/Base.lproj/Main.storyboard +++ b/iOS/Base.lproj/Main.storyboard @@ -214,9 +214,17 @@ + + + + + + + + @@ -289,6 +297,7 @@ + diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 2afb785b7..89315194b 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -14,6 +14,7 @@ import RSTree class MasterFeedViewController: UITableViewController, UndoableCommandRunner { + @IBOutlet weak var filterButton: UIBarButtonItem! private var refreshProgressView: RefreshProgressView? private var addNewItemButton: UIBarButtonItem! @@ -370,6 +371,16 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { coordinator.showSettings() } + @IBAction func toggleFilter(_ sender: Any) { + if coordinator.isUnreadFeedsFiltered { + filterButton.image = AppAssets.filterInactiveImage + coordinator.showAllFeeds() + } else { + filterButton.image = AppAssets.filterActiveImage + coordinator.hideUnreadFeeds() + } + } + @IBAction func add(_ sender: UIBarButtonItem) { coordinator.showAdd(.feed) } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 7e89ea43f..78d02adf3 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -112,6 +112,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { return panelMode == .three } + var isUnreadFeedsFiltered: Bool { + return treeControllerDelegate.isUnreadFiltered + } + var rootNode: Node { return treeController.rootNode } @@ -484,6 +488,16 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } return 0 } + + func showAllFeeds() { + treeControllerDelegate.isUnreadFiltered = false + rebuildBackingStores() + } + + func hideUnreadFeeds() { + treeControllerDelegate.isUnreadFiltered = true + rebuildBackingStores() + } func expand(_ node: Node) { node.isExpanded = true