mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-01-22 07:13:58 +01:00
Persist article read filters across application launches. Issue #1349
This commit is contained in:
parent
991ecf2a71
commit
81c4756f97
@ -17,7 +17,7 @@ public enum ContainerIdentifier: Hashable {
|
||||
case account(String) // accountID
|
||||
case folder(String, String) // accountID, folderName
|
||||
|
||||
public var userInfo: [AnyHashable: Any] {
|
||||
public var userInfo: [AnyHashable: AnyHashable] {
|
||||
switch self {
|
||||
case .smartFeedController:
|
||||
return [
|
||||
@ -37,7 +37,7 @@ public enum ContainerIdentifier: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public init?(userInfo: [AnyHashable: Any]) {
|
||||
public init?(userInfo: [AnyHashable: AnyHashable]) {
|
||||
guard let type = userInfo["type"] as? String else { return nil }
|
||||
|
||||
switch type {
|
||||
|
@ -12,7 +12,7 @@ public protocol FeedIdentifiable {
|
||||
var feedID: FeedIdentifier? { get }
|
||||
}
|
||||
|
||||
public enum FeedIdentifier: CustomStringConvertible {
|
||||
public enum FeedIdentifier: CustomStringConvertible, Hashable {
|
||||
|
||||
case smartFeed(String) // String is a unique identifier
|
||||
case script(String) // String is a unique identifier
|
||||
@ -32,7 +32,7 @@ public enum FeedIdentifier: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
public var userInfo: [AnyHashable: Any] {
|
||||
public var userInfo: [AnyHashable: AnyHashable] {
|
||||
switch self {
|
||||
case .smartFeed(let id):
|
||||
return [
|
||||
@ -59,7 +59,7 @@ public enum FeedIdentifier: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
public init?(userInfo: [AnyHashable: Any]) {
|
||||
public init?(userInfo: [AnyHashable: AnyHashable]) {
|
||||
guard let type = userInfo["type"] as? String else { return nil }
|
||||
|
||||
switch type {
|
||||
|
@ -26,5 +26,5 @@ struct UserInfoKey {
|
||||
static let windowState = "windowState"
|
||||
static let containerExpandedWindowState = "containerExpandedWindowState"
|
||||
static let readFeedsFilterState = "readFeedsFilterState"
|
||||
|
||||
static let readArticlesFilterState = "readArticlesFilterState"
|
||||
}
|
||||
|
@ -103,15 +103,12 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
|
||||
// MARK: Actions
|
||||
@IBAction func toggleFilter(_ sender: Any) {
|
||||
switch coordinator.articleReadFilterType {
|
||||
case .none:
|
||||
filterButton.image = AppAssets.filterActiveImage
|
||||
coordinator.hideUnreadArticles()
|
||||
case .read:
|
||||
if coordinator.isReadArticlesFiltered {
|
||||
filterButton.image = AppAssets.filterInactiveImage
|
||||
coordinator.showAllArticles()
|
||||
case .alwaysRead:
|
||||
break
|
||||
} else {
|
||||
filterButton.image = AppAssets.filterActiveImage
|
||||
coordinator.hideReadArticles()
|
||||
}
|
||||
}
|
||||
|
||||
@ -524,17 +521,19 @@ private extension MasterTimelineViewController {
|
||||
navigationItem.titleView = titleView
|
||||
}
|
||||
|
||||
switch coordinator.articleReadFilterType {
|
||||
case .none:
|
||||
switch coordinator.timelineDefaultReadFilterType {
|
||||
case .none, .read:
|
||||
filterButton.isHidden = false
|
||||
filterButton.image = AppAssets.filterInactiveImage
|
||||
case .read:
|
||||
filterButton.isHidden = false
|
||||
filterButton.image = AppAssets.filterActiveImage
|
||||
case .alwaysRead:
|
||||
filterButton.isHidden = true
|
||||
}
|
||||
|
||||
if coordinator.isReadArticlesFiltered {
|
||||
filterButton.image = AppAssets.filterActiveImage
|
||||
} else {
|
||||
filterButton.image = AppAssets.filterInactiveImage
|
||||
}
|
||||
|
||||
tableView.selectRow(at: nil, animated: false, scrollPosition: .top)
|
||||
if dataSource.snapshot().itemIdentifiers(inSection: 0).count > 0 {
|
||||
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
|
||||
|
@ -67,6 +67,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
private var animatingChanges = false
|
||||
private var expandedTable = Set<ContainerIdentifier>()
|
||||
private var readFilterEnabledTable = [FeedIdentifier: Bool]()
|
||||
private var shadowTable = [[Node]]()
|
||||
private var lastSearchString = ""
|
||||
private var lastSearchScope: SearchScope? = nil
|
||||
@ -121,7 +122,17 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
return treeControllerDelegate.isReadFiltered
|
||||
}
|
||||
|
||||
var articleReadFilterType: ReadFilterType = .none
|
||||
var isReadArticlesFiltered: Bool {
|
||||
if let feedID = timelineFeed?.feedID, let readFilterEnabled = readFilterEnabledTable[feedID] {
|
||||
return readFilterEnabled
|
||||
} else {
|
||||
return timelineDefaultReadFilterType != .none
|
||||
}
|
||||
}
|
||||
|
||||
var timelineDefaultReadFilterType: ReadFilterType {
|
||||
return timelineFeed?.defaultReadFilterType ?? .none
|
||||
}
|
||||
|
||||
var rootNode: Node {
|
||||
return treeController.rootNode
|
||||
@ -537,12 +548,16 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
}
|
||||
|
||||
func showAllArticles() {
|
||||
articleReadFilterType = .none
|
||||
if let feedID = timelineFeed?.feedID {
|
||||
readFilterEnabledTable[feedID] = false
|
||||
}
|
||||
refreshTimeline()
|
||||
}
|
||||
|
||||
func hideUnreadArticles() {
|
||||
articleReadFilterType = .read
|
||||
func hideReadArticles() {
|
||||
if let feedID = timelineFeed?.feedID {
|
||||
readFilterEnabledTable[feedID] = true
|
||||
}
|
||||
refreshTimeline()
|
||||
}
|
||||
|
||||
@ -1215,7 +1230,6 @@ private extension SceneCoordinator {
|
||||
func setTimelineFeed(_ feed: Feed?, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
timelineFeed = feed
|
||||
timelineMiddleIndexPath = nil
|
||||
articleReadFilterType = feed?.defaultReadFilterType ?? .none
|
||||
|
||||
fetchAndReplaceArticlesAsync(animated: animated) {
|
||||
self.masterTimelineViewController?.reinitializeArticles()
|
||||
@ -1576,8 +1590,7 @@ private extension SceneCoordinator {
|
||||
precondition(Thread.isMainThread)
|
||||
cancelPendingAsyncFetches()
|
||||
|
||||
let readFilter = articleReadFilterType != .none
|
||||
let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: readFilter, representedObjects: representedObjects) { [weak self] (articles, operation) in
|
||||
let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: isReadArticlesFiltered, representedObjects: representedObjects) { [weak self] (articles, operation) in
|
||||
precondition(Thread.isMainThread)
|
||||
guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else {
|
||||
return
|
||||
@ -1763,10 +1776,15 @@ private extension SceneCoordinator {
|
||||
// MARK: NSUserActivity
|
||||
|
||||
func windowState() -> [AnyHashable: Any] {
|
||||
let containerIdentifierUserInfos = expandedTable.map( { $0.userInfo })
|
||||
let containerExpandedWindowState = expandedTable.map( { $0.userInfo })
|
||||
var readArticlesFilterState = [[AnyHashable: AnyHashable]: Bool]()
|
||||
for key in readFilterEnabledTable.keys {
|
||||
readArticlesFilterState[key.userInfo] = readFilterEnabledTable[key]
|
||||
}
|
||||
return [
|
||||
UserInfoKey.readFeedsFilterState: isReadFeedsFiltered,
|
||||
UserInfoKey.containerExpandedWindowState: containerIdentifierUserInfos
|
||||
UserInfoKey.containerExpandedWindowState: containerExpandedWindowState,
|
||||
UserInfoKey.readArticlesFilterState: readArticlesFilterState
|
||||
]
|
||||
}
|
||||
|
||||
@ -1774,15 +1792,22 @@ private extension SceneCoordinator {
|
||||
if let readFeedsFilterState = windowState[UserInfoKey.readFeedsFilterState] as? Bool {
|
||||
treeControllerDelegate.isReadFiltered = readFeedsFilterState
|
||||
}
|
||||
if let containerIdentifierUserInfos = windowState[UserInfoKey.containerExpandedWindowState] as? [[AnyHashable: Any]] {
|
||||
let containerIdentifers = containerIdentifierUserInfos.compactMap( { ContainerIdentifier(userInfo: $0) })
|
||||
if let containerExpandedWindowState = windowState[UserInfoKey.containerExpandedWindowState] as? [[AnyHashable: AnyHashable]] {
|
||||
let containerIdentifers = containerExpandedWindowState.compactMap( { ContainerIdentifier(userInfo: $0) })
|
||||
expandedTable = Set(containerIdentifers)
|
||||
}
|
||||
if let readArticlesFilterState = windowState[UserInfoKey.readArticlesFilterState] as? [[AnyHashable: AnyHashable]: Bool] {
|
||||
for key in readArticlesFilterState.keys {
|
||||
if let feedIdentifier = FeedIdentifier(userInfo: key) {
|
||||
readFilterEnabledTable[feedIdentifier] = readArticlesFilterState[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleSelectFeed(_ userInfo: [AnyHashable : Any]?) {
|
||||
guard let userInfo = userInfo,
|
||||
let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : Any],
|
||||
let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : AnyHashable],
|
||||
let feedIdentifier = FeedIdentifier(userInfo: feedIdentifierUserInfo) else {
|
||||
return
|
||||
}
|
||||
@ -1842,7 +1867,7 @@ private extension SceneCoordinator {
|
||||
}
|
||||
|
||||
func restoreFeed(_ userInfo: [AnyHashable : Any], accountID: String, webFeedID: String, articleID: String) -> Bool {
|
||||
guard let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : Any],
|
||||
guard let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : AnyHashable],
|
||||
let feedIdentifier = FeedIdentifier(userInfo: feedIdentifierUserInfo) else {
|
||||
return false
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user