Change from using @Published as a PassthroughSubject to using real ones to avoid @Published quirks

This commit is contained in:
Maurice Parker 2020-07-24 11:40:17 -05:00
parent 7c9484bba1
commit b2c70e847c
4 changed files with 105 additions and 85 deletions

View File

@ -115,8 +115,8 @@ extension SceneModel: SidebarModelDelegate {
extension SceneModel: TimelineModelDelegate { extension SceneModel: TimelineModelDelegate {
var selectedFeeds: Published<[Feed]>.Publisher { var selectedFeedsPublisher: AnyPublisher<[Feed], Never>? {
return sidebarModel.$selectedFeeds return sidebarModel.selectedFeedsPublisher
} }
func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed) { func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed) {

View File

@ -31,7 +31,7 @@ struct SidebarContextMenu: View {
#endif #endif
} }
Button { Button {
sidebarModel.markAllAsRead(account: sidebarItem.represented as! Account) // sidebarModel.markAllAsRead(account: sidebarItem.represented as! Account)
} label: { } label: {
Text("Mark All As Read") Text("Mark All As Read")
#if os(iOS) #if os(iOS)
@ -46,7 +46,7 @@ struct SidebarContextMenu: View {
guard let feed = sidebarItem.feed else { guard let feed = sidebarItem.feed else {
return return
} }
sidebarModel.markAllAsRead(feed: feed) sidebarModel.markAllAsReadInFeed.send(feed)
} label: { } label: {
Text("Mark All As Read") Text("Mark All As Read")
#if os(iOS) #if os(iOS)
@ -69,7 +69,7 @@ struct SidebarContextMenu: View {
guard let feed = sidebarItem.feed else { guard let feed = sidebarItem.feed else {
return return
} }
sidebarModel.markAllAsRead(feed: feed) sidebarModel.markAllAsReadInFeed.send(feed)
} label: { } label: {
Text("Mark All As Read") Text("Mark All As Read")
#if os(iOS) #if os(iOS)
@ -123,7 +123,7 @@ struct SidebarContextMenu: View {
} }
Divider() Divider()
Button { Button {
sidebarModel.deleteItems(item: sidebarItem) // sidebarModel.deleteItems(item: sidebarItem)
} label: { } label: {
Text("Delete") Text("Delete")
#if os(iOS) #if os(iOS)
@ -146,7 +146,7 @@ struct SidebarContextMenu: View {
guard let feed = sidebarItem.feed else { guard let feed = sidebarItem.feed else {
return return
} }
sidebarModel.markAllAsRead(feed: feed) sidebarModel.markAllAsReadInFeed.send(feed)
} label: { } label: {
Text("Mark All As Read") Text("Mark All As Read")
#if os(iOS) #if os(iOS)
@ -155,7 +155,7 @@ struct SidebarContextMenu: View {
} }
Divider() Divider()
Button { Button {
sidebarModel.deleteItems(item: sidebarItem) // sidebarModel.deleteItems(item: sidebarItem)
} label: { } label: {
Text("Delete") Text("Delete")
#if os(iOS) #if os(iOS)

View File

@ -18,15 +18,18 @@ protocol SidebarModelDelegate: class {
class SidebarModel: ObservableObject, UndoableCommandRunner { class SidebarModel: ObservableObject, UndoableCommandRunner {
@Published var selectedFeedIdentifiers = Set<FeedIdentifier>()
@Published var selectedFeedIdentifier: FeedIdentifier? = .none
@Published var isReadFiltered = false
weak var delegate: SidebarModelDelegate? weak var delegate: SidebarModelDelegate?
var sidebarItemsPublisher: AnyPublisher<[SidebarItem], Never>? var sidebarItemsPublisher: AnyPublisher<[SidebarItem], Never>?
var selectNextUnread = PassthroughSubject<Bool, Never>() var selectedFeedsPublisher: AnyPublisher<[Feed], Never>?
@Published var selectedFeedIdentifiers = Set<FeedIdentifier>() var selectNextUnread = PassthroughSubject<Bool, Never>()
@Published var selectedFeedIdentifier: FeedIdentifier? = .none var markAllAsReadInFeed = PassthroughSubject<Feed, Never>()
@Published var selectedFeeds = [Feed]() var markAllAsReadInAccount = PassthroughSubject<Feed, Never>()
@Published var isReadFiltered = false
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@ -37,29 +40,37 @@ class SidebarModel: ObservableObject, UndoableCommandRunner {
subscribeToSelectedFeedChanges() subscribeToSelectedFeedChanges()
subscribeToRebuildSidebarItemsEvents() subscribeToRebuildSidebarItemsEvents()
subscribeToNextUnread() subscribeToNextUnread()
subscribeToMarkAllAsReadInFeed()
} }
} }
// MARK: Side Context Menu Actions // MARK: Side Context Menu Actions
extension SidebarModel { private extension SidebarModel {
func markAllAsRead(feed: Feed) { func subscribeToMarkAllAsReadInFeed() {
var articles = Set<Article>() guard let selectedFeedsPublisher = selectedFeedsPublisher else { return }
let fetchedArticles = try! feed.fetchArticles()
for article in fetchedArticles {
articles.insert(article)
}
for selectedFeed in selectedFeeds { markAllAsReadInFeed
let fetchedArticles = try! selectedFeed.fetchArticles() .withLatestFrom(selectedFeedsPublisher, resultSelector: { givenFeed, selectedFeeds -> [Feed] in
for article in fetchedArticles { if selectedFeeds.contains(where: { $0.feedID == givenFeed.feedID }) {
articles.insert(article) return selectedFeeds
} else {
return [givenFeed]
}
})
.map { feeds in
var articles = [Article]()
for feed in feeds {
articles.append(contentsOf: (try? feed.fetchArticles()) ?? Set<Article>())
}
return articles
} }
} .sink { [weak self] allArticles in
self?.markAllAsRead(allArticles)
markAllAsRead(Array(articles)) }
.store(in: &cancellables)
} }
func markAllAsRead(account: Account) { func markAllAsRead(account: Account) {
@ -78,51 +89,50 @@ extension SidebarModel {
private func markAllAsRead(_ articles: [Article]) { private func markAllAsRead(_ articles: [Article]) {
guard let undoManager = undoManager ?? UndoManager(), guard let undoManager = undoManager ?? UndoManager(),
let markAsReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { let markAsReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else {
return return
} }
runCommand(markAsReadCommand) runCommand(markAsReadCommand)
} }
func deleteItems(item: SidebarItem) { func deleteItems(item: SidebarItem) {
#if os(macOS) // #if os(macOS)
if selectedFeeds.count > 0 { // if selectedFeeds.count > 0 {
for feed in selectedFeeds { // for feed in selectedFeeds {
if feed is WebFeed { // if feed is WebFeed {
print(feed.nameForDisplay) // print(feed.nameForDisplay)
let account = (feed as! WebFeed).account // let account = (feed as! WebFeed).account
account?.removeWebFeed(feed as! WebFeed) // account?.removeWebFeed(feed as! WebFeed)
} // }
if feed is Folder { // if feed is Folder {
let account = (feed as! Folder).account // let account = (feed as! Folder).account
account?.removeFolder(feed as! Folder, completion: { (result) in // account?.removeFolder(feed as! Folder, completion: { (result) in
switch result { // switch result {
case .success( _): // case .success( _):
print("Deleted folder") // print("Deleted folder")
case .failure(let err): // case .failure(let err):
print(err.localizedDescription) // print(err.localizedDescription)
} // }
}) // })
} // }
} // }
} // }
#else // #else
if item.feed is WebFeed { // if item.feed is WebFeed {
let account = (item.feed as! WebFeed).account // let account = (item.feed as! WebFeed).account
account?.removeWebFeed(item.feed as! WebFeed) // account?.removeWebFeed(item.feed as! WebFeed)
} // }
if item.feed is Folder { // if item.feed is Folder {
let account = (item.feed as! Folder).account // let account = (item.feed as! Folder).account
account?.removeFolder(item.feed as! Folder, completion: { (result) in // account?.removeFolder(item.feed as! Folder, completion: { (result) in
switch result { // switch result {
case .success( _): // case .success( _):
print("Deleted folder") // print("Deleted folder")
case .failure(let err): // case .failure(let err):
print(err.localizedDescription) // print(err.localizedDescription)
} // }
}) // })
} // }
#endif // #endif
} }
} }
@ -135,22 +145,31 @@ private extension SidebarModel {
// MARK: Subscriptions // MARK: Subscriptions
func subscribeToSelectedFeedChanges() { func subscribeToSelectedFeedChanges() {
$selectedFeedIdentifiers.map { [weak self] feedIDs in let selectedFeedIdentifersPublisher = $selectedFeedIdentifiers
feedIDs.compactMap { self?.findFeed($0) } .map { [weak self] feedIDs -> [Feed] in
} return feedIDs.compactMap { self?.findFeed($0) }
.assign(to: &$selectedFeeds)
$selectedFeedIdentifier.compactMap { [weak self] feedID in
if let feedID = feedID, let feed = self?.findFeed(feedID) {
return [feed]
} else {
return nil
} }
}
.assign(to: &$selectedFeeds) let selectedFeedIdentiferPublisher = $selectedFeedIdentifier
.compactMap { [weak self] feedID -> [Feed]? in
if let feedID = feedID, let feed = self?.findFeed(feedID) {
return [feed]
} else {
return nil
}
}
selectedFeedsPublisher = selectedFeedIdentifersPublisher
.merge(with: selectedFeedIdentiferPublisher)
.removeDuplicates(by: { previousFeeds, currentFeeds in
return previousFeeds.elementsEqual(currentFeeds, by: { $0.feedID == $1.feedID })
})
.eraseToAnyPublisher()
} }
func subscribeToRebuildSidebarItemsEvents() { func subscribeToRebuildSidebarItemsEvents() {
guard let selectedFeedsPublisher = selectedFeedsPublisher else { return }
let chidrenDidChangePublisher = NotificationCenter.default.publisher(for: .ChildrenDidChange) let chidrenDidChangePublisher = NotificationCenter.default.publisher(for: .ChildrenDidChange)
let batchUpdateDidPerformPublisher = NotificationCenter.default.publisher(for: .BatchUpdateDidPerform) let batchUpdateDidPerformPublisher = NotificationCenter.default.publisher(for: .BatchUpdateDidPerform)
let displayNameDidChangePublisher = NotificationCenter.default.publisher(for: .DisplayNameDidChange) let displayNameDidChangePublisher = NotificationCenter.default.publisher(for: .DisplayNameDidChange)
@ -173,7 +192,7 @@ private extension SidebarModel {
sidebarItemsPublisher = sidebarRebuildPublishers sidebarItemsPublisher = sidebarRebuildPublishers
.prepend(kickStarter) .prepend(kickStarter)
.debounce(for: .milliseconds(500), scheduler: RunLoop.main) .debounce(for: .milliseconds(500), scheduler: RunLoop.main)
.combineLatest($isReadFiltered, $selectedFeeds) .combineLatest($isReadFiltered.removeDuplicates(), selectedFeedsPublisher)
.compactMap { [weak self] _, readFilter, selectedFeeds in .compactMap { [weak self] _, readFilter, selectedFeeds in
self?.rebuildSidebarItems(isReadFiltered: readFilter, selectedFeeds: selectedFeeds) self?.rebuildSidebarItems(isReadFiltered: readFilter, selectedFeeds: selectedFeeds)
} }
@ -181,10 +200,10 @@ private extension SidebarModel {
} }
func subscribeToNextUnread() { func subscribeToNextUnread() {
guard let sidebarItemsPublisher = sidebarItemsPublisher else { return } guard let sidebarItemsPublisher = sidebarItemsPublisher, let selectedFeedsPublisher = selectedFeedsPublisher else { return }
selectNextUnread selectNextUnread
.withLatestFrom(sidebarItemsPublisher, $selectedFeeds) .withLatestFrom(sidebarItemsPublisher, selectedFeedsPublisher)
.compactMap { [weak self] (sidebarItems, selectedFeeds) in .compactMap { [weak self] (sidebarItems, selectedFeeds) in
return self?.nextUnread(sidebarItems: sidebarItems, selectedFeeds: selectedFeeds) return self?.nextUnread(sidebarItems: sidebarItems, selectedFeeds: selectedFeeds)
} }

View File

@ -17,7 +17,7 @@ import Account
import Articles import Articles
protocol TimelineModelDelegate: class { protocol TimelineModelDelegate: class {
var selectedFeeds: Published<[Feed]>.Publisher { get } var selectedFeedsPublisher: AnyPublisher<[Feed], Never>? { get }
func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed) func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed)
} }
@ -136,7 +136,8 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
} }
func subscribeToSelectedFeedChanges() { func subscribeToSelectedFeedChanges() {
delegate?.selectedFeeds.sink { [weak self] feeds in guard let selectedFeedsPublisher = delegate?.selectedFeedsPublisher else { return }
selectedFeedsPublisher.sink { [weak self] feeds in
guard let self = self else { return } guard let self = self else { return }
self.feeds = feeds self.feeds = feeds
self.fetchArticles() self.fetchArticles()