Make timeline filter maintain it state for each timeline individually
This commit is contained in:
parent
2ebec7801c
commit
d7a4bddc72
|
@ -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 selectedArticleIDs = Set<String>() // Don't use directly. Use selectedArticles
|
||||||
@Published var selectedArticleID: String? = .none // Don't use directly. Use selectedArticles
|
@Published var selectedArticleID: String? = .none // Don't use directly. Use selectedArticles
|
||||||
@Published var selectedArticles = [Article]()
|
@Published var selectedArticles = [Article]()
|
||||||
@Published var isReadFiltered = false
|
@Published var readFilterEnabledTable = [FeedIdentifier: Bool]()
|
||||||
|
@Published var isReadFiltered: Bool? = nil
|
||||||
|
|
||||||
var undoManager: UndoManager?
|
var undoManager: UndoManager?
|
||||||
var undoableCommands = [UndoableCommand]()
|
var undoableCommands = [UndoableCommand]()
|
||||||
|
@ -33,8 +34,8 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
||||||
private var selectedArticleIDsCancellable: AnyCancellable?
|
private var selectedArticleIDsCancellable: AnyCancellable?
|
||||||
private var selectedArticleIDCancellable: AnyCancellable?
|
private var selectedArticleIDCancellable: AnyCancellable?
|
||||||
private var selectedArticlesCancellable: AnyCancellable?
|
private var selectedArticlesCancellable: AnyCancellable?
|
||||||
private var selectedReadFilteredCancellable: AnyCancellable?
|
|
||||||
|
|
||||||
|
private var feeds = [Feed]()
|
||||||
private var fetchSerialNumber = 0
|
private var fetchSerialNumber = 0
|
||||||
private let fetchRequestQueue = FetchRequestQueue()
|
private let fetchRequestQueue = FetchRequestQueue()
|
||||||
private var exceptionArticleFetcher: ArticleFetcher?
|
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
|
// MARK: API
|
||||||
|
|
||||||
func fetchArticles(feeds: [Feed]) {
|
func fetchArticles(feeds: [Feed]) {
|
||||||
|
self.feeds = feeds
|
||||||
|
|
||||||
if feeds.count == 1 {
|
if feeds.count == 1 {
|
||||||
nameForDisplay = feeds.first!.nameForDisplay
|
nameForDisplay = feeds.first!.nameForDisplay
|
||||||
} else {
|
} else {
|
||||||
nameForDisplay = NSLocalizedString("Multiple", comment: "Multiple Feeds")
|
nameForDisplay = NSLocalizedString("Multiple", comment: "Multiple Feeds")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetReadFilter()
|
||||||
fetchAndReplaceArticlesAsync(feeds: feeds)
|
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() {
|
func toggleReadStatusForSelectedArticles() {
|
||||||
guard !selectedArticles.isEmpty else {
|
guard !selectedArticles.isEmpty else {
|
||||||
return
|
return
|
||||||
|
@ -207,6 +215,24 @@ private extension TimelineModel {
|
||||||
|
|
||||||
// MARK: Timeline Management
|
// 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() {
|
func sortParametersDidChange() {
|
||||||
performBlockAndRestoreSelection {
|
performBlockAndRestoreSelection {
|
||||||
let unsortedArticles = Set(articles)
|
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.
|
// if it’s been superseded by a newer fetch, or the timeline was emptied, etc., it won’t get called.
|
||||||
precondition(Thread.isMainThread)
|
precondition(Thread.isMainThread)
|
||||||
cancelPendingAsyncFetches()
|
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)
|
precondition(Thread.isMainThread)
|
||||||
guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else {
|
guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else {
|
||||||
return
|
return
|
||||||
|
@ -269,11 +297,12 @@ private extension TimelineModel {
|
||||||
// TODO: Update unread counts and other item done in didSet on AppKit
|
// 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 }
|
let selectedArticleIDs = selectedArticles.map { $0.articleID }
|
||||||
|
|
||||||
timelineItems = articles.compactMap { article in
|
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
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return TimelineItem(article: article)
|
return TimelineItem(article: article)
|
||||||
|
|
|
@ -19,15 +19,17 @@ struct TimelineToolbarModifier: ViewModifier {
|
||||||
ToolbarItem(placement: .navigation) {
|
ToolbarItem(placement: .navigation) {
|
||||||
Button (action: {
|
Button (action: {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
timelineModel.isReadFiltered.toggle()
|
timelineModel.toggleReadFilter()
|
||||||
}
|
}
|
||||||
}, label: {
|
}, label: {
|
||||||
if timelineModel.isReadFiltered {
|
if timelineModel.isReadFiltered ?? false {
|
||||||
AppAssets.filterActiveImage.font(.title3)
|
AppAssets.filterActiveImage.font(.title3)
|
||||||
} else {
|
} else {
|
||||||
AppAssets.filterInactiveImage.font(.title3)
|
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 {
|
ToolbarItem {
|
||||||
|
|
|
@ -20,18 +20,19 @@ struct TimelineView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
Button (action: {
|
Button (action: {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
timelineModel.isReadFiltered.toggle()
|
timelineModel.toggleReadFilter()
|
||||||
}
|
}
|
||||||
}, label: {
|
}, label: {
|
||||||
if timelineModel.isReadFiltered {
|
if timelineModel.isReadFiltered ?? false {
|
||||||
AppAssets.filterActiveImage
|
AppAssets.filterActiveImage
|
||||||
} else {
|
} else {
|
||||||
AppAssets.filterInactiveImage
|
AppAssets.filterInactiveImage
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.hidden(timelineModel.isReadFiltered == nil)
|
||||||
.padding(.top, 8).padding(.trailing)
|
.padding(.top, 8).padding(.trailing)
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
.help(timelineModel.isReadFiltered ? "Show Read Articles" : "Filter Read Articles")
|
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
||||||
}
|
}
|
||||||
ZStack {
|
ZStack {
|
||||||
NavigationLink(destination: ArticleContainerView(articles: timelineModel.selectedArticles), isActive: $navigate) {
|
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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
|
||||||
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; };
|
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; };
|
||||||
519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionPointViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2687,6 +2690,7 @@
|
||||||
children = (
|
children = (
|
||||||
514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */,
|
514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */,
|
||||||
5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */,
|
5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */,
|
||||||
|
5194736D24BBB937001A2939 /* HiddenModifier.swift */,
|
||||||
);
|
);
|
||||||
path = "SwiftUI Extensions";
|
path = "SwiftUI Extensions";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -4992,6 +4996,7 @@
|
||||||
FF64D0E724AF53EE0084080A /* RefreshProgressModel.swift in Sources */,
|
FF64D0E724AF53EE0084080A /* RefreshProgressModel.swift in Sources */,
|
||||||
51E4996A24A8762D00B667CB /* ExtractedArticle.swift in Sources */,
|
51E4996A24A8762D00B667CB /* ExtractedArticle.swift in Sources */,
|
||||||
51919FF124AB864A00541E64 /* TimelineModel.swift in Sources */,
|
51919FF124AB864A00541E64 /* TimelineModel.swift in Sources */,
|
||||||
|
5194736E24BBB937001A2939 /* HiddenModifier.swift in Sources */,
|
||||||
51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */,
|
51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */,
|
||||||
51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */,
|
51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */,
|
||||||
51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */,
|
51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */,
|
||||||
|
@ -5119,6 +5124,7 @@
|
||||||
51E4993A24A8708800B667CB /* AppDelegate.swift in Sources */,
|
51E4993A24A8708800B667CB /* AppDelegate.swift in Sources */,
|
||||||
51E498CE24A8085D00B667CB /* UnreadFeed.swift in Sources */,
|
51E498CE24A8085D00B667CB /* UnreadFeed.swift in Sources */,
|
||||||
51E498C724A8085D00B667CB /* StarredFeedDelegate.swift in Sources */,
|
51E498C724A8085D00B667CB /* StarredFeedDelegate.swift in Sources */,
|
||||||
|
5194736F24BBB937001A2939 /* HiddenModifier.swift in Sources */,
|
||||||
51919FB724AABCA100541E64 /* IconImageView.swift in Sources */,
|
51919FB724AABCA100541E64 /* IconImageView.swift in Sources */,
|
||||||
51B54A6924B54A490014348B /* IconView.swift in Sources */,
|
51B54A6924B54A490014348B /* IconView.swift in Sources */,
|
||||||
51E498FA24A808BA00B667CB /* SingleFaviconDownloader.swift in Sources */,
|
51E498FA24A808BA00B667CB /* SingleFaviconDownloader.swift in Sources */,
|
||||||
|
|
Loading…
Reference in New Issue