Add sidebar read filter

This commit is contained in:
Maurice Parker 2020-07-12 10:52:42 -05:00
parent 9f4a037c8f
commit 22e2c0b0e6
4 changed files with 103 additions and 52 deletions

View File

@ -23,9 +23,11 @@ class SidebarModel: ObservableObject, UndoableCommandRunner {
@Published var selectedFeedIdentifiers = Set<FeedIdentifier>() @Published var selectedFeedIdentifiers = Set<FeedIdentifier>()
@Published var selectedFeedIdentifier: FeedIdentifier? = .none @Published var selectedFeedIdentifier: FeedIdentifier? = .none
@Published var selectedFeeds = [Feed]() @Published var selectedFeeds = [Feed]()
@Published var isReadFiltered = false
private var selectedFeedIdentifiersCancellable: AnyCancellable? private var selectedFeedIdentifiersCancellable: AnyCancellable?
private var selectedFeedIdentifierCancellable: AnyCancellable? private var selectedFeedIdentifierCancellable: AnyCancellable?
private var selectedReadFilteredCancellable: AnyCancellable?
private let rebuildSidebarItemsQueue = CoalescingQueue(name: "Rebuild The Sidebar Items", interval: 0.5) private let rebuildSidebarItemsQueue = CoalescingQueue(name: "Rebuild The Sidebar Items", interval: 0.5)
@ -55,41 +57,20 @@ class SidebarModel: ObservableObject, UndoableCommandRunner {
self.selectedFeeds = [feed] self.selectedFeeds = [feed]
} }
} }
selectedReadFilteredCancellable = $isReadFiltered.sink { [weak self] filter in
guard let self = self else { return }
self.rebuildSidebarItems(isReadFiltered: filter)
}
} }
// MARK: API // MARK: API
@objc func rebuildSidebarItems() { /// Rebuilds the sidebar items to cause the sidebar to rebuild itself
guard let delegate = delegate else { return } func rebuildSidebarItems() {
var items = [SidebarItem]() rebuildSidebarItemsWithCurrentValues()
var smartFeedControllerItem = SidebarItem(SmartFeedsController.shared)
for feed in SmartFeedsController.shared.smartFeeds {
smartFeedControllerItem.addChild(SidebarItem(feed, unreadCount: delegate.unreadCount(for: feed)))
}
items.append(smartFeedControllerItem)
for account in AccountManager.shared.sortedActiveAccounts {
var accountItem = SidebarItem(account)
for webFeed in sort(account.topLevelWebFeeds) {
accountItem.addChild(SidebarItem(webFeed, unreadCount: delegate.unreadCount(for: webFeed)))
}
for folder in sort(account.folders ?? Set<Folder>()) {
var folderItem = SidebarItem(folder, unreadCount: delegate.unreadCount(for: folder))
for webFeed in sort(folder.topLevelWebFeeds) {
folderItem.addChild(SidebarItem(webFeed, unreadCount: delegate.unreadCount(for: webFeed)))
}
accountItem.addChild(folderItem)
}
items.append(accountItem)
}
sidebarItems = items
} }
} }
// MARK: Private // MARK: Private
@ -114,16 +95,61 @@ private extension SidebarModel {
} }
func queueRebuildSidebarItems() { func queueRebuildSidebarItems() {
rebuildSidebarItemsQueue.add(self, #selector(rebuildSidebarItems)) rebuildSidebarItemsQueue.add(self, #selector(rebuildSidebarItemsWithCurrentValues))
} }
@objc func rebuildSidebarItemsWithCurrentValues() {
rebuildSidebarItems(isReadFiltered: isReadFiltered)
}
func rebuildSidebarItems(isReadFiltered: Bool) {
guard let delegate = delegate else { return }
var items = [SidebarItem]()
var smartFeedControllerItem = SidebarItem(SmartFeedsController.shared)
for feed in SmartFeedsController.shared.smartFeeds {
// It looks like SwiftUI loses its mind when the last element in a section is removed. Don't filter
// the smartfeeds yet or we crash about everytime because Starred is almost always filtered
// if !isReadFiltered || feed.unreadCount > 0 {
smartFeedControllerItem.addChild(SidebarItem(feed, unreadCount: delegate.unreadCount(for: feed)))
// }
}
items.append(smartFeedControllerItem)
for account in AccountManager.shared.sortedActiveAccounts {
var accountItem = SidebarItem(account)
for webFeed in sort(account.topLevelWebFeeds) {
if !isReadFiltered || webFeed.unreadCount > 0 {
accountItem.addChild(SidebarItem(webFeed, unreadCount: delegate.unreadCount(for: webFeed)))
}
}
for folder in sort(account.folders ?? Set<Folder>()) {
if !isReadFiltered || folder.unreadCount > 0 {
var folderItem = SidebarItem(folder, unreadCount: delegate.unreadCount(for: folder))
for webFeed in sort(folder.topLevelWebFeeds) {
if !isReadFiltered || webFeed.unreadCount > 0 {
folderItem.addChild(SidebarItem(webFeed, unreadCount: delegate.unreadCount(for: webFeed)))
}
}
accountItem.addChild(folderItem)
}
}
items.append(accountItem)
}
sidebarItems = items
}
// MARK: Notifications // MARK: Notifications
@objc func unreadCountDidInitialize(_ notification: Notification) { @objc func unreadCountDidInitialize(_ notification: Notification) {
guard notification.object is AccountManager else { guard notification.object is AccountManager else {
return return
} }
rebuildSidebarItems() rebuildSidebarItems(isReadFiltered: isReadFiltered)
} }
@objc func unreadCountDidChange(_ note: Notification) { @objc func unreadCountDidChange(_ note: Notification) {
@ -135,27 +161,27 @@ private extension SidebarModel {
} }
@objc func containerChildrenDidChange(_ notification: Notification) { @objc func containerChildrenDidChange(_ notification: Notification) {
rebuildSidebarItems() rebuildSidebarItems(isReadFiltered: isReadFiltered)
} }
@objc func batchUpdateDidPerform(_ notification: Notification) { @objc func batchUpdateDidPerform(_ notification: Notification) {
rebuildSidebarItems() rebuildSidebarItems(isReadFiltered: isReadFiltered)
} }
@objc func displayNameDidChange(_ note: Notification) { @objc func displayNameDidChange(_ note: Notification) {
rebuildSidebarItems() rebuildSidebarItems(isReadFiltered: isReadFiltered)
} }
@objc func accountStateDidChange(_ note: Notification) { @objc func accountStateDidChange(_ note: Notification) {
rebuildSidebarItems() rebuildSidebarItems(isReadFiltered: isReadFiltered)
} }
@objc func userDidAddAccount(_ note: Notification) { @objc func userDidAddAccount(_ note: Notification) {
rebuildSidebarItems() rebuildSidebarItems(isReadFiltered: isReadFiltered)
} }
@objc func userDidDeleteAccount(_ note: Notification) { @objc func userDidDeleteAccount(_ note: Notification) {
rebuildSidebarItems() rebuildSidebarItems(isReadFiltered: isReadFiltered)
} }
} }

View File

@ -10,6 +10,7 @@ import SwiftUI
struct SidebarToolbarModifier: ViewModifier { struct SidebarToolbarModifier: ViewModifier {
@EnvironmentObject private var sidebarModel: SidebarModel
@EnvironmentObject private var defaults: AppDefaults @EnvironmentObject private var defaults: AppDefaults
@StateObject private var viewModel = SidebarToolbarModel() @StateObject private var viewModel = SidebarToolbarModel()
@ -19,10 +20,16 @@ struct SidebarToolbarModifier: ViewModifier {
.toolbar { .toolbar {
ToolbarItem(placement: .navigation) { ToolbarItem(placement: .navigation) {
Button(action: { Button (action: {
withAnimation {
sidebarModel.isReadFiltered.toggle()
}
}, label: { }, label: {
AppAssets.filterInactiveImage if sidebarModel.isReadFiltered {
.font(.title3) AppAssets.filterActiveImage.font(.title3)
} else {
AppAssets.filterInactiveImage.font(.title3)
}
}).help("Filter Read Feeds") }).help("Filter Read Feeds")
} }

View File

@ -19,16 +19,34 @@ struct SidebarView: View {
@ViewBuilder var body: some View { @ViewBuilder var body: some View {
#if os(macOS) #if os(macOS)
ZStack { VStack {
NavigationLink(destination: TimelineContainerView(feeds: sidebarModel.selectedFeeds), isActive: $navigate) { HStack {
EmptyView() Spacer()
}.hidden() Button (action: {
List(selection: $sidebarModel.selectedFeedIdentifiers) { withAnimation {
rows sidebarModel.isReadFiltered.toggle()
}
}, label: {
if sidebarModel.isReadFiltered {
AppAssets.filterActiveImage
} else {
AppAssets.filterInactiveImage
}
})
.padding(.top).padding(.trailing)
.buttonStyle(PlainButtonStyle())
}
ZStack {
NavigationLink(destination: TimelineContainerView(feeds: sidebarModel.selectedFeeds), isActive: $navigate) {
EmptyView()
}.hidden()
List(selection: $sidebarModel.selectedFeedIdentifiers) {
rows
}
}
.onChange(of: sidebarModel.selectedFeedIdentifiers) { value in
navigate = !sidebarModel.selectedFeedIdentifiers.isEmpty
} }
}
.onChange(of: sidebarModel.selectedFeedIdentifiers) { value in
navigate = !sidebarModel.selectedFeedIdentifiers.isEmpty
} }
#else #else
List { List {

View File

@ -25,7 +25,8 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
@Published var selectedArticleIDs = Set<String>() @Published var selectedArticleIDs = Set<String>()
@Published var selectedArticleID: String? = .none @Published var selectedArticleID: String? = .none
@Published var selectedArticles = [Article]() @Published var selectedArticles = [Article]()
@Published var isReadFiltered = false
var undoManager: UndoManager? var undoManager: UndoManager?
var undoableCommands = [UndoableCommand]() var undoableCommands = [UndoableCommand]()
@ -36,7 +37,6 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
private var fetchSerialNumber = 0 private var fetchSerialNumber = 0
private let fetchRequestQueue = FetchRequestQueue() private let fetchRequestQueue = FetchRequestQueue()
private var exceptionArticleFetcher: ArticleFetcher? private var exceptionArticleFetcher: ArticleFetcher?
private var isReadFiltered = false
private var articles = [Article]() { private var articles = [Article]() {
didSet { didSet {