Standardize notification handling on Combine instead of selectors

This commit is contained in:
Maurice Parker 2020-07-18 15:20:15 -05:00
parent 5845925b3a
commit 876f978347
5 changed files with 116 additions and 154 deletions

View File

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

View File

@ -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<AnyCancellable>()
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) {

View File

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

View File

@ -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<AnyCancellable>()
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)
NotificationCenter.default.publisher(for: .StatusesDidChange).sink { [weak self] note in
guard let self = self, let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set<String> else {
return
}
for i in 0..<self.timelineItems.count {
if articleIDs.contains(self.timelineItems[i].article.articleID) {
self.timelineItems[i].updateStatus()
}
}
}.store(in: &cancellables)
NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { [weak self] _ in
self?.sortDirection = AppDefaults.shared.timelineSortDirection
self?.groupByFeed = AppDefaults.shared.timelineGroupByFeed
}.store(in: &cancellables)
// TODO: This should be rewritten to use Combine correctly
selectedFeedsCancellable = delegate?.selectedFeeds.sink { [weak self] feeds in
delegate?.selectedFeeds.sink { [weak self] feeds in
guard let self = self else { return }
self.fetchArticles(feeds: feeds)
}
}.store(in: &cancellables)
// TODO: This should be rewritten to use Combine correctly
selectedArticleIDsCancellable = $selectedArticleIDs.sink { [weak self] articleIDs in
$selectedArticleIDs.sink { [weak self] articleIDs in
guard let self = self else { return }
self.selectedArticles = articleIDs.compactMap { self.idToArticleDictionary[$0] }
}
}.store(in: &cancellables)
// TODO: This should be rewritten to use Combine correctly
selectedArticleIDCancellable = $selectedArticleID.sink { [weak self] articleID in
$selectedArticleID.sink { [weak self] articleID in
guard let self = self else { return }
if let articleID = articleID, let article = self.idToArticleDictionary[articleID] {
self.selectedArticles = [article]
}
}
}.store(in: &cancellables)
// TODO: This should be rewritten to use Combine correctly
selectedArticlesCancellable = $selectedArticles.sink { articles in
$selectedArticles.sink { articles in
if articles.count == 1 {
let article = articles.first!
if !article.status.read {
markArticles(Set([article]), statusKey: .read, flag: true)
}
}
}
}.store(in: &cancellables)
}
// MARK: API
@ -325,24 +334,6 @@ private extension TimelineModel {
runCommand(markReadCommand)
}
// MARK: Notifications
@objc func statusesDidChange(_ note: Notification) {
guard let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set<String> else {
return
}
for i in 0..<timelineItems.count {
if articleIDs.contains(timelineItems[i].article.articleID) {
timelineItems[i].updateStatus()
}
}
}
@objc func userDefaultsDidChange(_ note: Notification) {
sortDirection = AppDefaults.shared.timelineSortDirection
groupByFeed = AppDefaults.shared.timelineGroupByFeed
}
// MARK: Timeline Management
func resetReadFilter() {

View File

@ -63,22 +63,22 @@ class AccountsPreferencesModel: ObservableObject {
@Published var showDeleteConfirmation: Bool = false
// Subscriptions
var notificationSubscriptions = Set<AnyCancellable>()
var cancellables = Set<AnyCancellable>()
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: &notificationSubscriptions)
}.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: &notificationSubscriptions)
}.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: &notificationSubscriptions)
}.store(in: &cancellables)
}
}