diff --git a/Multiplatform/Shared/Images/ArticleIconImageLoader.swift b/Multiplatform/Shared/Images/ArticleIconImageLoader.swift index ec08f5189..cc94abe57 100644 --- a/Multiplatform/Shared/Images/ArticleIconImageLoader.swift +++ b/Multiplatform/Shared/Images/ArticleIconImageLoader.swift @@ -7,19 +7,40 @@ // import Foundation +import Combine import Account import Articles final class ArticleIconImageLoader: ObservableObject { - private var article: Article? - @Published var image: IconImage? - + private var article: Article? + private var cancellables = Set() + init() { - NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) + NotificationCenter.default.publisher(for: .FaviconDidBecomeAvailable).sink { [weak self] _ in + guard let self = self, let article = self.article else { return } + self.image = article.iconImage() + }.store(in: &cancellables) + + NotificationCenter.default.publisher(for: .WebFeedIconDidBecomeAvailable).sink { [weak self] note in + guard let self = self, let article = self.article, let noteFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, noteFeed == article.webFeed else { + return + } + self.image = article.iconImage() + }.store(in: &cancellables) + + NotificationCenter.default.publisher(for: .AvatarDidBecomeAvailable).sink { [weak self] note in + guard let self = self, let article = self.article, let authors = article.authors, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else { + return + } + for author in authors { + if author.avatarURL == avatarURL { + self.image = article.iconImage() + return + } + } + }.store(in: &cancellables) } func loadImage(for article: Article) { @@ -29,31 +50,3 @@ final class ArticleIconImageLoader: ObservableObject { } } - -private extension ArticleIconImageLoader { - - @objc func faviconDidBecomeAvailable(_ note: Notification) { - guard let article = article else { return } - image = article.iconImage() - } - - @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { - guard let article = article, let noteFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, noteFeed == article.webFeed else { - return - } - image = article.iconImage() - } - - @objc func avatarDidBecomeAvailable(_ note: Notification) { - guard let article = article, let authors = article.authors, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else { - return - } - - for author in authors { - if author.avatarURL == avatarURL { - image = article.iconImage() - return - } - } - } -} diff --git a/Multiplatform/Shared/Images/FeedIconImageLoader.swift b/Multiplatform/Shared/Images/FeedIconImageLoader.swift index 6a6fa8586..9ee4d4bef 100644 --- a/Multiplatform/Shared/Images/FeedIconImageLoader.swift +++ b/Multiplatform/Shared/Images/FeedIconImageLoader.swift @@ -7,17 +7,27 @@ // import SwiftUI +import Combine import Account final class FeedIconImageLoader: ObservableObject { - private var feed: Feed? - @Published var image: IconImage? + private var feed: Feed? + private var cancellables = Set() init() { - NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) + NotificationCenter.default.publisher(for: .FaviconDidBecomeAvailable).sink { [weak self] _ in + self?.fetchImage() + }.store(in: &cancellables) + + + NotificationCenter.default.publisher(for: .WebFeedIconDidBecomeAvailable).sink { [weak self] note in + guard let feed = self?.feed as? WebFeed, let noteFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, feed == noteFeed else { + return + } + self?.fetchImage() + }.store(in: &cancellables) } func loadImage(for feed: Feed) { @@ -30,17 +40,6 @@ final class FeedIconImageLoader: ObservableObject { private extension FeedIconImageLoader { - @objc func faviconDidBecomeAvailable(_ note: Notification) { - fetchImage() - } - - @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { - guard let feed = feed as? WebFeed, let noteFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, feed == noteFeed else { - return - } - fetchImage() - } - func fetchImage() { if let webFeed = feed as? WebFeed { if let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed) { diff --git a/Multiplatform/Shared/Sidebar/SidebarModel.swift b/Multiplatform/Shared/Sidebar/SidebarModel.swift index b81c18ad9..1d4cb2632 100644 --- a/Multiplatform/Shared/Sidebar/SidebarModel.swift +++ b/Multiplatform/Shared/Sidebar/SidebarModel.swift @@ -25,9 +25,7 @@ class SidebarModel: ObservableObject, UndoableCommandRunner { @Published var selectedFeeds = [Feed]() @Published var isReadFiltered = false - private var selectedFeedIdentifiersCancellable: AnyCancellable? - private var selectedFeedIdentifierCancellable: AnyCancellable? - private var selectedReadFilteredCancellable: AnyCancellable? + private var cancellables = Set() private let rebuildSidebarItemsQueue = CoalescingQueue(name: "Rebuild The Sidebar Items", interval: 0.5) @@ -35,33 +33,55 @@ class SidebarModel: ObservableObject, UndoableCommandRunner { var undoableCommands = [UndoableCommand]() init() { - NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidInitialize(_:)), name: .UnreadCountDidInitialize, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(userDidAddAccount(_:)), name: .UserDidAddAccount, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(userDidDeleteAccount(_:)), name: .UserDidDeleteAccount, object: nil) - + NotificationCenter.default.publisher(for: .UnreadCountDidInitialize) + .filter { $0.object is AccountManager } + .sink { [weak self] note in + guard let self = self else { return } + self.rebuildSidebarItems(isReadFiltered: self.isReadFiltered) + }.store(in: &cancellables) + + NotificationCenter.default.publisher(for: .UnreadCountDidChange) + .filter { _ in AccountManager.shared.isUnreadCountsInitialized } + .sink { [weak self] _ in + self?.queueRebuildSidebarItems() + }.store(in: &cancellables) + + let chidrenDidChangePublisher = NotificationCenter.default.publisher(for: .ChildrenDidChange) + let batchUpdateDidPerformPublisher = NotificationCenter.default.publisher(for: .BatchUpdateDidPerform) + let displayNameDidChangePublisher = NotificationCenter.default.publisher(for: .DisplayNameDidChange) + let accountStateDidChangePublisher = NotificationCenter.default.publisher(for: .AccountStateDidChange) + let userDidAddAccountPublisher = NotificationCenter.default.publisher(for: .UserDidAddAccount) + let userDidDeleteAccountPublisher = NotificationCenter.default.publisher(for: .UserDidDeleteAccount) + + let sidebarRebuildPublishers = chidrenDidChangePublisher.merge(with: batchUpdateDidPerformPublisher, + displayNameDidChangePublisher, + accountStateDidChangePublisher, + userDidAddAccountPublisher, + userDidDeleteAccountPublisher) + + sidebarRebuildPublishers.sink { [weak self] _ in + guard let self = self else { return } + self.rebuildSidebarItems(isReadFiltered: self.isReadFiltered) + }.store(in: &cancellables) + + // TODO: This should be rewritten to use Combine correctly - selectedFeedIdentifiersCancellable = $selectedFeedIdentifiers.sink { [weak self] feedIDs in + $selectedFeedIdentifiers.sink { [weak self] feedIDs in guard let self = self else { return } self.selectedFeeds = feedIDs.compactMap { self.findFeed($0) } - } + }.store(in: &cancellables) // TODO: This should be rewritten to use Combine correctly - selectedFeedIdentifierCancellable = $selectedFeedIdentifier.sink { [weak self] feedID in + $selectedFeedIdentifier.sink { [weak self] feedID in guard let self = self else { return } if let feedID = feedID, let feed = self.findFeed(feedID) { self.selectedFeeds = [feed] } - } + }.store(in: &cancellables) - selectedReadFilteredCancellable = $isReadFiltered.sink { [weak self] filter in - guard let self = self else { return } - self.rebuildSidebarItems(isReadFiltered: filter) - } + $isReadFiltered.sink { [weak self] filter in + self?.rebuildSidebarItems(isReadFiltered: filter) + }.store(in: &cancellables) } // MARK: API @@ -143,45 +163,4 @@ private extension SidebarModel { sidebarItems = items } - // MARK: Notifications - - @objc func unreadCountDidInitialize(_ notification: Notification) { - guard notification.object is AccountManager else { - return - } - rebuildSidebarItems(isReadFiltered: isReadFiltered) - } - - @objc func unreadCountDidChange(_ note: Notification) { - // We will handle the filtering of unread feeds in unreadCountDidInitialize after they have all be calculated - guard AccountManager.shared.isUnreadCountsInitialized else { - return - } - queueRebuildSidebarItems() - } - - @objc func containerChildrenDidChange(_ notification: Notification) { - rebuildSidebarItems(isReadFiltered: isReadFiltered) - } - - @objc func batchUpdateDidPerform(_ notification: Notification) { - rebuildSidebarItems(isReadFiltered: isReadFiltered) - } - - @objc func displayNameDidChange(_ note: Notification) { - rebuildSidebarItems(isReadFiltered: isReadFiltered) - } - - @objc func accountStateDidChange(_ note: Notification) { - rebuildSidebarItems(isReadFiltered: isReadFiltered) - } - - @objc func userDidAddAccount(_ note: Notification) { - rebuildSidebarItems(isReadFiltered: isReadFiltered) - } - - @objc func userDidDeleteAccount(_ note: Notification) { - rebuildSidebarItems(isReadFiltered: isReadFiltered) - } - } diff --git a/Multiplatform/Shared/Timeline/TimelineModel.swift b/Multiplatform/Shared/Timeline/TimelineModel.swift index 859d18740..681f74055 100644 --- a/Multiplatform/Shared/Timeline/TimelineModel.swift +++ b/Multiplatform/Shared/Timeline/TimelineModel.swift @@ -42,10 +42,7 @@ class TimelineModel: ObservableObject, UndoableCommandRunner { var undoManager: UndoManager? var undoableCommands = [UndoableCommand]() - private var selectedFeedsCancellable: AnyCancellable? - private var selectedArticleIDsCancellable: AnyCancellable? - private var selectedArticleIDCancellable: AnyCancellable? - private var selectedArticlesCancellable: AnyCancellable? + private var cancellables = Set() private var feeds = [Feed]() private var fetchSerialNumber = 0 @@ -78,39 +75,51 @@ class TimelineModel: ObservableObject, UndoableCommandRunner { } func startup() { - NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) - - // TODO: This should be rewritten to use Combine correctly - selectedFeedsCancellable = delegate?.selectedFeeds.sink { [weak self] feeds in - guard let self = self else { return } - self.fetchArticles(feeds: feeds) - } + NotificationCenter.default.publisher(for: .StatusesDidChange).sink { [weak self] note in + guard let self = self, let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set else { + return + } + for i in 0.. else { - return - } - for i in 0..() + var cancellables = Set() init() { sortedAccounts = AccountManager.shared.sortedAccounts - NotificationCenter.default.publisher(for: .UserDidAddAccount).sink(receiveValue: { [weak self] _ in + NotificationCenter.default.publisher(for: .UserDidAddAccount).sink { [weak self] _ in self?.sortedAccounts = AccountManager.shared.sortedAccounts - }).store(in: ¬ificationSubscriptions) + }.store(in: &cancellables) - NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink(receiveValue: { [weak self] _ in + NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink { [weak self] _ in self?.selectedConfiguredAccountID = nil self?.sortedAccounts = AccountManager.shared.sortedAccounts self?.selectedConfiguredAccountID = AccountManager.shared.defaultAccount.accountID - }).store(in: ¬ificationSubscriptions) + }.store(in: &cancellables) - NotificationCenter.default.publisher(for: .AccountStateDidChange).sink(receiveValue: { [weak self] notification in + NotificationCenter.default.publisher(for: .AccountStateDidChange).sink { [weak self] notification in guard let account = notification.object as? Account else { return } @@ -87,7 +87,7 @@ class AccountsPreferencesModel: ObservableObject { self?.accountIsActive = account.isActive self?.accountName = account.name ?? "" } - }).store(in: ¬ificationSubscriptions) + }.store(in: &cancellables) } }