Reimplement Article Read Filter

This commit is contained in:
Maurice Parker 2020-07-25 19:14:59 -05:00
parent 8f346af250
commit 59528f48b1
3 changed files with 70 additions and 53 deletions

View File

@ -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>()
} }

View File

@ -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
}
} }
} }

View File

@ -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