Make timeline filter maintain it state for each timeline individually
This commit is contained in:
parent
2ebec7801c
commit
d7a4bddc72
21
Multiplatform/Shared/SwiftUI Extensions/HiddenModifier.swift
Normal file
21
Multiplatform/Shared/SwiftUI Extensions/HiddenModifier.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 it’s been superseded by a newer fetch, or the timeline was emptied, etc., it won’t 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)
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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 */,
|
||||
|
Loading…
x
Reference in New Issue
Block a user