mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-03 12:27:32 +01:00
Reimplement Article Read Filter
This commit is contained in:
parent
8f346af250
commit
59528f48b1
@ -28,7 +28,6 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
|||||||
@Published var nameForDisplay = ""
|
@Published var nameForDisplay = ""
|
||||||
@Published var selectedTimelineItemIDs = Set<String>() // Don't use directly. Use selectedTimelineItemsPublisher
|
@Published var selectedTimelineItemIDs = Set<String>() // Don't use directly. Use selectedTimelineItemsPublisher
|
||||||
@Published var selectedTimelineItemID: String? = nil // Don't use directly. Use selectedTimelineItemsPublisher
|
@Published var selectedTimelineItemID: String? = nil // Don't use directly. Use selectedTimelineItemsPublisher
|
||||||
@Published var isReadFiltered: Bool? = nil
|
|
||||||
|
|
||||||
var selectedArticles = [Article]()
|
var selectedArticles = [Article]()
|
||||||
|
|
||||||
@ -37,11 +36,13 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
|||||||
var selectedTimelineItemsPublisher: AnyPublisher<[TimelineItem], Never>?
|
var selectedTimelineItemsPublisher: AnyPublisher<[TimelineItem], Never>?
|
||||||
var selectedArticlesPublisher: AnyPublisher<[Article], Never>?
|
var selectedArticlesPublisher: AnyPublisher<[Article], Never>?
|
||||||
var articleStatusChangePublisher: AnyPublisher<Set<String>, Never>?
|
var articleStatusChangePublisher: AnyPublisher<Set<String>, Never>?
|
||||||
|
var readFilterAndFeedsPublisher: AnyPublisher<([Feed], Bool?), Never>?
|
||||||
|
|
||||||
var markAllAsReadSubject = PassthroughSubject<Void, Never>()
|
var markAllAsReadSubject = PassthroughSubject<Void, Never>()
|
||||||
var toggleReadStatusForSelectedArticlesSubject = PassthroughSubject<Void, Never>()
|
var toggleReadStatusForSelectedArticlesSubject = PassthroughSubject<Void, Never>()
|
||||||
var toggleStarredStatusForSelectedArticlesSubject = PassthroughSubject<Void, Never>()
|
var toggleStarredStatusForSelectedArticlesSubject = PassthroughSubject<Void, Never>()
|
||||||
var openSelectedArticlesInBrowserSubject = PassthroughSubject<Void, Never>()
|
var openSelectedArticlesInBrowserSubject = PassthroughSubject<Void, Never>()
|
||||||
|
var changeReadFilterSubject = PassthroughSubject<Bool, Never>()
|
||||||
|
|
||||||
var readFilterEnabledTable = [FeedIdentifier: Bool]()
|
var readFilterEnabledTable = [FeedIdentifier: Bool]()
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
|||||||
init(delegate: TimelineModelDelegate) {
|
init(delegate: TimelineModelDelegate) {
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
subscribeToUserDefaultsChanges()
|
subscribeToUserDefaultsChanges()
|
||||||
subscribeToReadFilterChanges()
|
subscribeToReadFilterAndFeedChanges()
|
||||||
subscribeToArticleFetchChanges()
|
subscribeToArticleFetchChanges()
|
||||||
subscribeToArticleSelectionChanges()
|
subscribeToArticleSelectionChanges()
|
||||||
subscribeToArticleStatusChanges()
|
subscribeToArticleStatusChanges()
|
||||||
@ -67,15 +68,6 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
|||||||
subscribeToOpenInBrowserEvents()
|
subscribeToOpenInBrowserEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: API
|
|
||||||
|
|
||||||
func toggleReadFilter() {
|
|
||||||
// guard let filter = isReadFiltered, let feedID = feeds.first?.feedID else { return }
|
|
||||||
// readFilterEnabledTable[feedID] = !filter
|
|
||||||
// isReadFiltered = !filter
|
|
||||||
// self.fetchArticles()
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func goToNextUnread() -> Bool {
|
func goToNextUnread() -> Bool {
|
||||||
// var startIndex: Int
|
// var startIndex: Int
|
||||||
@ -142,32 +134,6 @@ private extension TimelineModel {
|
|||||||
// }.store(in: &cancellables)
|
// }.store(in: &cancellables)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// TODO: Don't forget to redo this!!!
|
|
||||||
func subscribeToReadFilterChanges() {
|
|
||||||
guard let selectedFeedsPublisher = delegate?.selectedFeedsPublisher else { return }
|
|
||||||
|
|
||||||
selectedFeedsPublisher.sink { [weak self] feeds in
|
|
||||||
guard let self = self else { return }
|
|
||||||
|
|
||||||
guard feeds.count == 1, let timelineFeed = feeds.first else {
|
|
||||||
self.isReadFiltered = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard timelineFeed.defaultReadFilterType != .alwaysRead else {
|
|
||||||
self.isReadFiltered = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let feedID = timelineFeed.feedID, let readFilterEnabled = self.readFilterEnabledTable[feedID] {
|
|
||||||
self.isReadFiltered = readFilterEnabled
|
|
||||||
} else {
|
|
||||||
self.isReadFiltered = timelineFeed.defaultReadFilterType == .read
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
func subscribeToUserDefaultsChanges() {
|
func subscribeToUserDefaultsChanges() {
|
||||||
let kickStartNote = Notification(name: Notification.Name("Kick Start"))
|
let kickStartNote = Notification(name: Notification.Name("Kick Start"))
|
||||||
NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)
|
NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)
|
||||||
@ -178,17 +144,60 @@ private extension TimelineModel {
|
|||||||
}.store(in: &cancellables)
|
}.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribeToArticleFetchChanges() {
|
func subscribeToReadFilterAndFeedChanges() {
|
||||||
guard let selectedFeedsPublisher = delegate?.selectedFeedsPublisher else { return }
|
guard let selectedFeedsPublisher = delegate?.selectedFeedsPublisher else { return }
|
||||||
|
|
||||||
|
let toggledReadFilterPublisher = changeReadFilterSubject
|
||||||
|
.map { Optional($0) }
|
||||||
|
.withLatestFrom(selectedFeedsPublisher, resultSelector: { ($1, $0) })
|
||||||
|
.share()
|
||||||
|
|
||||||
|
toggledReadFilterPublisher
|
||||||
|
.sink { [weak self] (selectedFeeds, readFiltered) in
|
||||||
|
if let feedID = selectedFeeds.first?.feedID {
|
||||||
|
self?.readFilterEnabledTable[feedID] = readFiltered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
let feedsReadFilterPublisher = selectedFeedsPublisher
|
||||||
|
.map { [weak self] feeds -> ([Feed], Bool?) in
|
||||||
|
guard let self = self else { return (feeds, nil) }
|
||||||
|
|
||||||
|
guard feeds.count == 1, let timelineFeed = feeds.first else {
|
||||||
|
return (feeds, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard timelineFeed.defaultReadFilterType != .alwaysRead else {
|
||||||
|
return (feeds, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let feedID = timelineFeed.feedID, let readFilterEnabled = self.readFilterEnabledTable[feedID] {
|
||||||
|
return (feeds, readFilterEnabled)
|
||||||
|
} else {
|
||||||
|
return (feeds, timelineFeed.defaultReadFilterType == .read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readFilterAndFeedsPublisher = toggledReadFilterPublisher
|
||||||
|
.merge(with: feedsReadFilterPublisher)
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
func subscribeToArticleFetchChanges() {
|
||||||
|
guard let readFilterAndFeedsPublisher = readFilterAndFeedsPublisher,
|
||||||
|
let selectedFeedsPublisher = delegate?.selectedFeedsPublisher else { return }
|
||||||
|
|
||||||
let sortDirectionPublisher = sortDirectionSubject.removeDuplicates()
|
let sortDirectionPublisher = sortDirectionSubject.removeDuplicates()
|
||||||
let groupByPublisher = groupByFeedSubject.removeDuplicates()
|
let groupByPublisher = groupByFeedSubject.removeDuplicates()
|
||||||
|
|
||||||
timelineItemsPublisher = selectedFeedsPublisher
|
timelineItemsPublisher = readFilterAndFeedsPublisher
|
||||||
.map { [weak self] feeds -> Set<Article> in
|
.map { [weak self] (feeds, readFilter) -> Set<Article> in
|
||||||
return self?.fetchArticles(feeds: feeds) ?? Set<Article>()
|
return self?.fetchArticles(feeds: feeds, isReadFiltered: readFilter) ?? Set<Article>()
|
||||||
}
|
}
|
||||||
.combineLatest(sortDirectionPublisher, groupByPublisher)
|
.combineLatest(sortDirectionPublisher, groupByPublisher)
|
||||||
.compactMap { [weak self] articles, sortDirection, groupBy in
|
.compactMap { [weak self] articles, sortDirection, groupBy in
|
||||||
|
print("************")
|
||||||
let sortedArticles = Array(articles).sortedByDate(sortDirection ? .orderedDescending : .orderedAscending, groupByFeed: groupBy)
|
let sortedArticles = Array(articles).sortedByDate(sortDirection ? .orderedDescending : .orderedAscending, groupByFeed: groupBy)
|
||||||
return self?.buildTimelineItems(articles: sortedArticles) ?? TimelineItems()
|
return self?.buildTimelineItems(articles: sortedArticles) ?? TimelineItems()
|
||||||
}
|
}
|
||||||
@ -343,7 +352,7 @@ private extension TimelineModel {
|
|||||||
|
|
||||||
// MARK: Article Fetching
|
// MARK: Article Fetching
|
||||||
|
|
||||||
func fetchArticles(feeds: [Feed]) -> Set<Article> {
|
func fetchArticles(feeds: [Feed], isReadFiltered: Bool?) -> Set<Article> {
|
||||||
if feeds.isEmpty {
|
if feeds.isEmpty {
|
||||||
return Set<Article>()
|
return Set<Article>()
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ struct TimelineToolbarModifier: ViewModifier {
|
|||||||
|
|
||||||
@EnvironmentObject private var sceneModel: SceneModel
|
@EnvironmentObject private var sceneModel: SceneModel
|
||||||
@EnvironmentObject private var timelineModel: TimelineModel
|
@EnvironmentObject private var timelineModel: TimelineModel
|
||||||
|
@State private var isReadFiltered: Bool? = nil
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
@ -19,18 +20,18 @@ struct TimelineToolbarModifier: ViewModifier {
|
|||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
if let filter = isReadFiltered {
|
||||||
timelineModel.toggleReadFilter()
|
timelineModel.changeReadFilterSubject.send(!filter)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
if timelineModel.isReadFiltered ?? false {
|
if isReadFiltered ?? false {
|
||||||
AppAssets.filterActiveImage.font(.title3)
|
AppAssets.filterActiveImage.font(.title3)
|
||||||
} else {
|
} else {
|
||||||
AppAssets.filterInactiveImage.font(.title3)
|
AppAssets.filterInactiveImage.font(.title3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.hidden(timelineModel.isReadFiltered == nil)
|
.hidden(isReadFiltered == nil)
|
||||||
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
.help(isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolbarItem(placement: .bottomBar) {
|
ToolbarItem(placement: .bottomBar) {
|
||||||
@ -48,6 +49,9 @@ struct TimelineToolbarModifier: ViewModifier {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
.onReceive(timelineModel.readFilterAndFeedsPublisher!) { (_, filtered) in
|
||||||
|
isReadFiltered = filtered
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ struct TimelineView: View {
|
|||||||
@EnvironmentObject private var timelineModel: TimelineModel
|
@EnvironmentObject private var timelineModel: TimelineModel
|
||||||
@State private var timelineItems = TimelineItems()
|
@State private var timelineItems = TimelineItems()
|
||||||
@State private var timelineItemFrames = [String: CGRect]()
|
@State private var timelineItemFrames = [String: CGRect]()
|
||||||
|
@State private var isReadFiltered: Bool? = nil
|
||||||
|
|
||||||
@ViewBuilder var body: some View {
|
@ViewBuilder var body: some View {
|
||||||
GeometryReader { geometryReaderProxy in
|
GeometryReader { geometryReaderProxy in
|
||||||
@ -22,20 +23,20 @@ struct TimelineView: View {
|
|||||||
TimelineSortOrderView()
|
TimelineSortOrderView()
|
||||||
Spacer()
|
Spacer()
|
||||||
Button (action: {
|
Button (action: {
|
||||||
withAnimation {
|
if let filtered = isReadFiltered {
|
||||||
timelineModel.toggleReadFilter()
|
timelineModel.changeReadFilterSubject.send(!filtered)
|
||||||
}
|
}
|
||||||
}, label: {
|
}, label: {
|
||||||
if timelineModel.isReadFiltered ?? false {
|
if isReadFiltered ?? false {
|
||||||
AppAssets.filterActiveImage
|
AppAssets.filterActiveImage
|
||||||
} else {
|
} else {
|
||||||
AppAssets.filterInactiveImage
|
AppAssets.filterInactiveImage
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.hidden(timelineModel.isReadFiltered == nil)
|
.hidden(isReadFiltered == nil)
|
||||||
.padding(.top, 8).padding(.trailing)
|
.padding(.top, 8).padding(.trailing)
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
.help(isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
||||||
}
|
}
|
||||||
ScrollViewReader { scrollViewProxy in
|
ScrollViewReader { scrollViewProxy in
|
||||||
List(timelineItems.items, selection: $timelineModel.selectedTimelineItemIDs) { timelineItem in
|
List(timelineItems.items, selection: $timelineModel.selectedTimelineItemIDs) { timelineItem in
|
||||||
@ -62,6 +63,9 @@ struct TimelineView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onReceive(timelineModel.readFilterAndFeedsPublisher!) { (_, filtered) in
|
||||||
|
isReadFiltered = filtered
|
||||||
|
}
|
||||||
.onReceive(timelineModel.timelineItemsPublisher!) { items in
|
.onReceive(timelineModel.timelineItemsPublisher!) { items in
|
||||||
withAnimation {
|
withAnimation {
|
||||||
timelineItems = items
|
timelineItems = items
|
||||||
|
Loading…
x
Reference in New Issue
Block a user