diff --git a/Multiplatform/Shared/Sidebar/SidebarModel.swift b/Multiplatform/Shared/Sidebar/SidebarModel.swift index 76d228a7a..b81c18ad9 100644 --- a/Multiplatform/Shared/Sidebar/SidebarModel.swift +++ b/Multiplatform/Shared/Sidebar/SidebarModel.swift @@ -23,9 +23,11 @@ class SidebarModel: ObservableObject, UndoableCommandRunner { @Published var selectedFeedIdentifiers = Set() @Published var selectedFeedIdentifier: FeedIdentifier? = .none @Published var selectedFeeds = [Feed]() + @Published var isReadFiltered = false private var selectedFeedIdentifiersCancellable: AnyCancellable? private var selectedFeedIdentifierCancellable: AnyCancellable? + private var selectedReadFilteredCancellable: AnyCancellable? private let rebuildSidebarItemsQueue = CoalescingQueue(name: "Rebuild The Sidebar Items", interval: 0.5) @@ -55,41 +57,20 @@ class SidebarModel: ObservableObject, UndoableCommandRunner { self.selectedFeeds = [feed] } } + + selectedReadFilteredCancellable = $isReadFiltered.sink { [weak self] filter in + guard let self = self else { return } + self.rebuildSidebarItems(isReadFiltered: filter) + } } // MARK: API - @objc func rebuildSidebarItems() { - guard let delegate = delegate else { return } - var items = [SidebarItem]() - - 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()) { - 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 + /// Rebuilds the sidebar items to cause the sidebar to rebuild itself + func rebuildSidebarItems() { + rebuildSidebarItemsWithCurrentValues() } - + } // MARK: Private @@ -114,16 +95,61 @@ private extension SidebarModel { } 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()) { + 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 @objc func unreadCountDidInitialize(_ notification: Notification) { guard notification.object is AccountManager else { return } - rebuildSidebarItems() + rebuildSidebarItems(isReadFiltered: isReadFiltered) } @objc func unreadCountDidChange(_ note: Notification) { @@ -135,27 +161,27 @@ private extension SidebarModel { } @objc func containerChildrenDidChange(_ notification: Notification) { - rebuildSidebarItems() + rebuildSidebarItems(isReadFiltered: isReadFiltered) } @objc func batchUpdateDidPerform(_ notification: Notification) { - rebuildSidebarItems() + rebuildSidebarItems(isReadFiltered: isReadFiltered) } @objc func displayNameDidChange(_ note: Notification) { - rebuildSidebarItems() + rebuildSidebarItems(isReadFiltered: isReadFiltered) } @objc func accountStateDidChange(_ note: Notification) { - rebuildSidebarItems() + rebuildSidebarItems(isReadFiltered: isReadFiltered) } @objc func userDidAddAccount(_ note: Notification) { - rebuildSidebarItems() + rebuildSidebarItems(isReadFiltered: isReadFiltered) } @objc func userDidDeleteAccount(_ note: Notification) { - rebuildSidebarItems() + rebuildSidebarItems(isReadFiltered: isReadFiltered) } } diff --git a/Multiplatform/Shared/Sidebar/SidebarToolbarModifier.swift b/Multiplatform/Shared/Sidebar/SidebarToolbarModifier.swift index 22b539215..4eb1f3fd0 100644 --- a/Multiplatform/Shared/Sidebar/SidebarToolbarModifier.swift +++ b/Multiplatform/Shared/Sidebar/SidebarToolbarModifier.swift @@ -10,6 +10,7 @@ import SwiftUI struct SidebarToolbarModifier: ViewModifier { + @EnvironmentObject private var sidebarModel: SidebarModel @EnvironmentObject private var defaults: AppDefaults @StateObject private var viewModel = SidebarToolbarModel() @@ -19,10 +20,16 @@ struct SidebarToolbarModifier: ViewModifier { .toolbar { ToolbarItem(placement: .navigation) { - Button(action: { + Button (action: { + withAnimation { + sidebarModel.isReadFiltered.toggle() + } }, label: { - AppAssets.filterInactiveImage - .font(.title3) + if sidebarModel.isReadFiltered { + AppAssets.filterActiveImage.font(.title3) + } else { + AppAssets.filterInactiveImage.font(.title3) + } }).help("Filter Read Feeds") } diff --git a/Multiplatform/Shared/Sidebar/SidebarView.swift b/Multiplatform/Shared/Sidebar/SidebarView.swift index 09288dff3..4c66c9ab6 100644 --- a/Multiplatform/Shared/Sidebar/SidebarView.swift +++ b/Multiplatform/Shared/Sidebar/SidebarView.swift @@ -19,16 +19,34 @@ struct SidebarView: View { @ViewBuilder var body: some View { #if os(macOS) - ZStack { - NavigationLink(destination: TimelineContainerView(feeds: sidebarModel.selectedFeeds), isActive: $navigate) { - EmptyView() - }.hidden() - List(selection: $sidebarModel.selectedFeedIdentifiers) { - rows + VStack { + HStack { + Spacer() + Button (action: { + withAnimation { + 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 List { diff --git a/Multiplatform/Shared/Timeline/TimelineModel.swift b/Multiplatform/Shared/Timeline/TimelineModel.swift index 901a0286e..ffa50b0f5 100644 --- a/Multiplatform/Shared/Timeline/TimelineModel.swift +++ b/Multiplatform/Shared/Timeline/TimelineModel.swift @@ -25,7 +25,8 @@ class TimelineModel: ObservableObject, UndoableCommandRunner { @Published var selectedArticleIDs = Set() @Published var selectedArticleID: String? = .none @Published var selectedArticles = [Article]() - + @Published var isReadFiltered = false + var undoManager: UndoManager? var undoableCommands = [UndoableCommand]() @@ -36,7 +37,6 @@ class TimelineModel: ObservableObject, UndoableCommandRunner { private var fetchSerialNumber = 0 private let fetchRequestQueue = FetchRequestQueue() private var exceptionArticleFetcher: ArticleFetcher? - private var isReadFiltered = false private var articles = [Article]() { didSet {