Make timeline filter maintain it state for each timeline individually

This commit is contained in:
Maurice Parker 2020-07-12 16:48:39 -05:00
parent 2ebec7801c
commit d7a4bddc72
5 changed files with 74 additions and 15 deletions

View File

@ -0,0 +1,21 @@
//
// HiddenModifier.swift
// NetNewsWire
//
// Created by Maurice Parker on 7/12/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
extension View {
func hidden(_ hide: Bool) -> some View {
Group {
if hide {
self.hidden()
} else {
self
}
}
}
}

View File

@ -25,7 +25,8 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
@Published var selectedArticleIDs = Set<String>() // Don't use directly. Use selectedArticles
@Published var selectedArticleID: String? = .none // Don't use directly. Use selectedArticles
@Published var selectedArticles = [Article]()
@Published var isReadFiltered = false
@Published var readFilterEnabledTable = [FeedIdentifier: Bool]()
@Published var isReadFiltered: Bool? = nil
var undoManager: UndoManager?
var undoableCommands = [UndoableCommand]()
@ -33,8 +34,8 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
private var selectedArticleIDsCancellable: AnyCancellable?
private var selectedArticleIDCancellable: AnyCancellable?
private var selectedArticlesCancellable: AnyCancellable?
private var selectedReadFilteredCancellable: AnyCancellable?
private var feeds = [Feed]()
private var fetchSerialNumber = 0
private let fetchRequestQueue = FetchRequestQueue()
private var exceptionArticleFetcher: ArticleFetcher?
@ -97,23 +98,30 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
}
}
selectedReadFilteredCancellable = $isReadFiltered.sink { [weak self] filter in
guard let self = self else { return }
self.rebuildTimelineItems(isReadFiltered: filter)
}
}
// MARK: API
func fetchArticles(feeds: [Feed]) {
self.feeds = feeds
if feeds.count == 1 {
nameForDisplay = feeds.first!.nameForDisplay
} else {
nameForDisplay = NSLocalizedString("Multiple", comment: "Multiple Feeds")
}
resetReadFilter()
fetchAndReplaceArticlesAsync(feeds: feeds)
}
func toggleReadFilter() {
guard let filter = isReadFiltered, let feedID = feeds.first?.feedID else { return }
readFilterEnabledTable[feedID] = !filter
isReadFiltered = !filter
rebuildTimelineItems(isReadFiltered: isReadFiltered)
}
func toggleReadStatusForSelectedArticles() {
guard !selectedArticles.isEmpty else {
return
@ -207,6 +215,24 @@ private extension TimelineModel {
// MARK: Timeline Management
func resetReadFilter() {
guard feeds.count == 1, let timelineFeed = feeds.first else {
isReadFiltered = nil
return
}
guard timelineFeed.defaultReadFilterType != .alwaysRead else {
isReadFiltered = nil
return
}
if let feedID = timelineFeed.feedID, let readFilterEnabled = readFilterEnabledTable[feedID] {
isReadFiltered = readFilterEnabled
} else {
isReadFiltered = timelineFeed.defaultReadFilterType == .read
}
}
func sortParametersDidChange() {
performBlockAndRestoreSelection {
let unsortedArticles = Set(articles)
@ -253,7 +279,9 @@ private extension TimelineModel {
// if its been superseded by a newer fetch, or the timeline was emptied, etc., it wont get called.
precondition(Thread.isMainThread)
cancelPendingAsyncFetches()
let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: isReadFiltered, representedObjects: representedObjects) { [weak self] (articles, operation) in
let filtered = isReadFiltered ?? false
let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: filtered, representedObjects: representedObjects) { [weak self] (articles, operation) in
precondition(Thread.isMainThread)
guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else {
return
@ -269,11 +297,12 @@ private extension TimelineModel {
// TODO: Update unread counts and other item done in didSet on AppKit
}
func rebuildTimelineItems(isReadFiltered: Bool) {
func rebuildTimelineItems(isReadFiltered: Bool?) {
let filtered = isReadFiltered ?? false
let selectedArticleIDs = selectedArticles.map { $0.articleID }
timelineItems = articles.compactMap { article in
if isReadFiltered && article.status.read && !selectedArticleIDs.contains(article.articleID) {
if filtered && article.status.read && !selectedArticleIDs.contains(article.articleID) {
return nil
} else {
return TimelineItem(article: article)

View File

@ -19,15 +19,17 @@ struct TimelineToolbarModifier: ViewModifier {
ToolbarItem(placement: .navigation) {
Button (action: {
withAnimation {
timelineModel.isReadFiltered.toggle()
timelineModel.toggleReadFilter()
}
}, label: {
if timelineModel.isReadFiltered {
if timelineModel.isReadFiltered ?? false {
AppAssets.filterActiveImage.font(.title3)
} else {
AppAssets.filterInactiveImage.font(.title3)
}
}).help(timelineModel.isReadFiltered ? "Show Read Articles" : "Filter Read Articles")
})
.hidden(timelineModel.isReadFiltered == nil)
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
}
ToolbarItem {

View File

@ -20,18 +20,19 @@ struct TimelineView: View {
Spacer()
Button (action: {
withAnimation {
timelineModel.isReadFiltered.toggle()
timelineModel.toggleReadFilter()
}
}, label: {
if timelineModel.isReadFiltered {
if timelineModel.isReadFiltered ?? false {
AppAssets.filterActiveImage
} else {
AppAssets.filterInactiveImage
}
})
.hidden(timelineModel.isReadFiltered == nil)
.padding(.top, 8).padding(.trailing)
.buttonStyle(PlainButtonStyle())
.help(timelineModel.isReadFiltered ? "Show Read Articles" : "Filter Read Articles")
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
}
ZStack {
NavigationLink(destination: ArticleContainerView(articles: timelineModel.selectedArticles), isActive: $navigate) {

View File

@ -296,6 +296,8 @@
5193CD58245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; };
5193CD59245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; };
5193CD5A245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; };
5194736E24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; };
5194736F24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; };
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; };
519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; };
@ -1960,6 +1962,7 @@
51934CCD2310792F006127BE /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = "<group>"; };
51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = "<group>"; };
5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RedditFeedProvider-Extensions.swift"; sourceTree = "<group>"; };
5194736D24BBB937001A2939 /* HiddenModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenModifier.swift; sourceTree = "<group>"; };
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionPointViewController.swift; sourceTree = "<group>"; };
@ -2687,6 +2690,7 @@
children = (
514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */,
5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */,
5194736D24BBB937001A2939 /* HiddenModifier.swift */,
);
path = "SwiftUI Extensions";
sourceTree = "<group>";
@ -4992,6 +4996,7 @@
FF64D0E724AF53EE0084080A /* RefreshProgressModel.swift in Sources */,
51E4996A24A8762D00B667CB /* ExtractedArticle.swift in Sources */,
51919FF124AB864A00541E64 /* TimelineModel.swift in Sources */,
5194736E24BBB937001A2939 /* HiddenModifier.swift in Sources */,
51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */,
51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */,
51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */,
@ -5119,6 +5124,7 @@
51E4993A24A8708800B667CB /* AppDelegate.swift in Sources */,
51E498CE24A8085D00B667CB /* UnreadFeed.swift in Sources */,
51E498C724A8085D00B667CB /* StarredFeedDelegate.swift in Sources */,
5194736F24BBB937001A2939 /* HiddenModifier.swift in Sources */,
51919FB724AABCA100541E64 /* IconImageView.swift in Sources */,
51B54A6924B54A490014348B /* IconView.swift in Sources */,
51E498FA24A808BA00B667CB /* SingleFaviconDownloader.swift in Sources */,