diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 9ed067d18..36b89dddb 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -637,6 +637,10 @@ extension MainWindowController: NSWindowDelegate { extension MainWindowController: SidebarDelegate { + var directlyMarkedAsUnreadArticles: Set
? { + return timelineContainerViewController?.currentTimelineViewController?.directlyMarkedAsUnreadArticles + } + func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?) { // Don’t update the timeline if it already has those objects. let representedObjectsAreTheSame = timelineContainerViewController?.regularTimelineViewControllerHasRepresentedObjects(selectedObjects) ?? false diff --git a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift index 46286cd64..4f1819daa 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift @@ -69,8 +69,16 @@ extension SidebarViewController { return } - let articles = unreadArticles(for: objects) - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) else { + var markableArticles = unreadArticles(for: objects) + if let directlyMarkedAsUnreadArticles = delegate?.directlyMarkedAsUnreadArticles { + markableArticles = markableArticles.subtracting(directlyMarkedAsUnreadArticles) + } + + guard let undoManager = undoManager, + let markReadCommand = MarkStatusCommand(initialArticles: markableArticles, + markingRead: true, + directlyMarked: false, + undoManager: undoManager) else { return } runCommand(markReadCommand) diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index 99b33cb90..8dbd08563 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -17,6 +17,7 @@ extension Notification.Name { } protocol SidebarDelegate: AnyObject { + var directlyMarkedAsUnreadArticles: Set
? { get } func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?) func unreadCount(for: AnyObject) -> Int func sidebarInvalidatedRestorationState(_: SidebarViewController) @@ -256,7 +257,11 @@ protocol SidebarDelegate: AnyObject { return } if AppDefaults.shared.feedDoubleClickMarkAsRead, let articles = try? singleSelectedWebFeed?.fetchUnreadArticles() { - if let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) { + if let undoManager = undoManager, + let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), + markingRead: true, + directlyMarked: false, + undoManager: undoManager) { runCommand(markReadCommand) } } diff --git a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift index b658d9c3f..88f0ac440 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift @@ -39,12 +39,12 @@ extension TimelineViewController { @objc func markArticlesReadFromContextualMenu(_ sender: Any?) { guard let articles = articles(from: sender) else { return } - markArticles(articles, read: true) + markArticles(articles, read: true, directlyMarked: true) } @objc func markArticlesUnreadFromContextualMenu(_ sender: Any?) { guard let articles = articles(from: sender) else { return } - markArticles(articles, read: false) + markArticles(articles, read: false, directlyMarked: true) } @objc func markAboveArticlesReadFromContextualMenu(_ sender: Any?) { @@ -59,14 +59,14 @@ extension TimelineViewController { @objc func markArticlesStarredFromContextualMenu(_ sender: Any?) { guard let articles = articles(from: sender) else { return } - markArticles(articles, starred: true) + markArticles(articles, starred: true, directlyMarked: true) } @objc func markArticlesUnstarredFromContextualMenu(_ sender: Any?) { guard let articles = articles(from: sender) else { return } - markArticles(articles, starred: false) + markArticles(articles, starred: false, directlyMarked: true) } @objc func selectFeedInSidebarFromContextualMenu(_ sender: Any?) { @@ -81,7 +81,11 @@ extension TimelineViewController { return } - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: feedArticles, markingRead: true, undoManager: undoManager) else { + guard let undoManager = undoManager, + let markReadCommand = MarkStatusCommand(initialArticles: feedArticles, + markingRead: true, + directlyMarked: false, + undoManager: undoManager) else { return } @@ -115,16 +119,21 @@ extension TimelineViewController { private extension TimelineViewController { - func markArticles(_ articles: [Article], read: Bool) { - markArticles(articles, statusKey: .read, flag: read) + func markArticles(_ articles: [Article], read: Bool, directlyMarked: Bool) { + markArticles(articles, statusKey: .read, flag: read, directlyMarked: directlyMarked) } - func markArticles(_ articles: [Article], starred: Bool) { - markArticles(articles, statusKey: .starred, flag: starred) + func markArticles(_ articles: [Article], starred: Bool, directlyMarked: Bool) { + markArticles(articles, statusKey: .starred, flag: starred, directlyMarked: directlyMarked) } - func markArticles(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool) { - guard let undoManager = undoManager, let markStatusCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager) else { + func markArticles(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool) { + guard let undoManager = undoManager, + let markStatusCommand = MarkStatusCommand(initialArticles: articles, + statusKey: statusKey, + flag: flag, + directlyMarked: directlyMarked, + undoManager: undoManager) else { return } diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 654e44813..c15a59a7b 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -123,10 +123,13 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr showFeedNames = .feed } + directlyMarkedAsUnreadArticles = Set
() articleRowMap = [String: [Int]]() tableView.reloadData() } } + + var directlyMarkedAsUnreadArticles = Set
() var unreadCount: Int = 0 { didSet { @@ -219,6 +222,8 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .UserDidDeleteAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidDirectMarking(_:)), name: .MarkStatusCommandDidDirectMarking, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidUndoDirectMarking(_:)), name: .MarkStatusCommandDidUndoDirectMarking, object: nil) didRegisterForNotifications = true } } @@ -230,7 +235,13 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr // MARK: - API func markAllAsRead(completion: (() -> Void)? = nil) { - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager, completion: completion) else { + let markableArticles = Set(articles).subtracting(directlyMarkedAsUnreadArticles) + guard let undoManager = undoManager, + let markReadCommand = MarkStatusCommand(initialArticles: markableArticles, + markingRead: true, + directlyMarked: false, + undoManager: undoManager, + completion: completion) else { return } runCommand(markReadCommand) @@ -336,14 +347,22 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @IBAction func markSelectedArticlesAsRead(_ sender: Any?) { - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else { + guard let undoManager = undoManager, + let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, + markingRead: true, + directlyMarked: true, + undoManager: undoManager) else { return } runCommand(markReadCommand) } @IBAction func markSelectedArticlesAsUnread(_ sender: Any?) { - guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else { + guard let undoManager = undoManager, + let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, + markingRead: false, + directlyMarked: true, + undoManager: undoManager) else { return } runCommand(markUnreadCommand) @@ -411,7 +430,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr return } - guard let undoManager = undoManager, let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: markingRead, undoManager: undoManager) else { + guard let undoManager = undoManager, + let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, + markingRead: markingRead, + directlyMarked: true, + undoManager: undoManager) else { return } @@ -434,7 +457,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr return } - guard let undoManager = undoManager, let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, markingStarred: starring, undoManager: undoManager) else { + guard let undoManager = undoManager, + let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, + markingStarred: starring, + directlyMarked: true, + undoManager: undoManager) else { return } runCommand(markStarredCommand) @@ -501,7 +528,12 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr return } - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { + let markableArticles = Set(articlesToMark).subtracting(directlyMarkedAsUnreadArticles) + guard let undoManager = undoManager, + let markReadCommand = MarkStatusCommand(initialArticles: markableArticles, + markingRead: true, + directlyMarked: false, + undoManager: undoManager) else { return } runCommand(markReadCommand) @@ -509,9 +541,16 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr func markAboveArticlesRead(_ selectedArticles: [Article]) { guard let first = selectedArticles.first else { return } + let articlesToMark = articles.articlesAbove(article: first) guard !articlesToMark.isEmpty else { return } - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { + + let markableArticles = Set(articlesToMark).subtracting(directlyMarkedAsUnreadArticles) + guard let undoManager = undoManager, + let markReadCommand = MarkStatusCommand(initialArticles: markableArticles, + markingRead: true, + directlyMarked: false, + undoManager: undoManager) else { return } runCommand(markReadCommand) @@ -519,9 +558,16 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr func markBelowArticlesRead(_ selectedArticles: [Article]) { guard let last = selectedArticles.last else { return } + let articlesToMark = articles.articlesBelow(article: last) guard !articlesToMark.isEmpty else { return } - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { + + let markableArticles = Set(articlesToMark).subtracting(directlyMarkedAsUnreadArticles) + guard let undoManager = undoManager, + let markReadCommand = MarkStatusCommand(initialArticles: markableArticles, + markingRead: true, + directlyMarked: false, + undoManager: undoManager) else { return } runCommand(markReadCommand) @@ -665,6 +711,28 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr self.groupByFeed = AppDefaults.shared.timelineGroupByFeed } + @objc func markStatusCommandDidDirectMarking(_ note: Notification) { + guard let userInfo = note.userInfo, + let articles = userInfo[Account.UserInfoKey.articles] as? Set
, + let statusKey = userInfo[Account.UserInfoKey.statusKey] as? ArticleStatus.Key, + let flag = userInfo[Account.UserInfoKey.statusFlag] as? Bool else { return } + + if statusKey == .read && flag == false { + directlyMarkedAsUnreadArticles.formUnion(articles) + } + } + + @objc func markStatusCommandDidUndoDirectMarking(_ note: Notification) { + guard let userInfo = note.userInfo, + let articles = userInfo[Account.UserInfoKey.articles] as? Set
, + let statusKey = userInfo[Account.UserInfoKey.statusKey] as? ArticleStatus.Key, + let flag = userInfo[Account.UserInfoKey.statusFlag] as? Bool else { return } + + if statusKey == .read && flag == false { + directlyMarkedAsUnreadArticles.subtract(articles) + } + } + // MARK: - Reloading Data private func cellForRowView(_ rowView: NSView) -> NSView? { @@ -899,14 +967,22 @@ extension TimelineViewController: NSTableViewDelegate { } private func toggleArticleRead(_ article: Article) { - guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: [article], markingRead: !article.status.read, undoManager: undoManager) else { + guard let undoManager = undoManager, + let markUnreadCommand = MarkStatusCommand(initialArticles: [article], + markingRead: !article.status.read, + directlyMarked: true, + undoManager: undoManager) else { return } self.runCommand(markUnreadCommand) } - + private func toggleArticleStarred(_ article: Article) { - guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: [article], markingStarred: !article.status.starred, undoManager: undoManager) else { + guard let undoManager = undoManager, + let markUnreadCommand = MarkStatusCommand(initialArticles: [article], + markingStarred: !article.status.starred, + directlyMarked: true, + undoManager: undoManager) else { return } self.runCommand(markUnreadCommand) diff --git a/Shared/Commands/MarkStatusCommand.swift b/Shared/Commands/MarkStatusCommand.swift index 79cf9777c..aa3e39079 100644 --- a/Shared/Commands/MarkStatusCommand.swift +++ b/Shared/Commands/MarkStatusCommand.swift @@ -8,9 +8,20 @@ import Foundation import RSCore +import Account import Articles // Mark articles read/unread, starred/unstarred, deleted/undeleted. +// +// Directly marked articles are ones that were statused by selecting with a cursor or were selected by group. +// Indirectly marked articles didn't have any focus and were picked up using a Mark All command like Mark All as Read. +// +// See discussion for details: https://github.com/Ranchero-Software/NetNewsWire/issues/3734 + +public extension Notification.Name { + static let MarkStatusCommandDidDirectMarking = Notification.Name("MarkStatusCommandDid√DirectMarking") + static let MarkStatusCommandDidUndoDirectMarking = Notification.Name("MarkStatusCommandDidUndoDirectMarking") +} final class MarkStatusCommand: UndoableCommand { @@ -19,10 +30,11 @@ final class MarkStatusCommand: UndoableCommand { let articles: Set
let undoManager: UndoManager let flag: Bool + let directlyMarked: Bool let statusKey: ArticleStatus.Key var completion: (() -> Void)? = nil - init?(initialArticles: [Article], statusKey: ArticleStatus.Key, flag: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) { + init?(initialArticles: Set
, statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) { // Filter out articles that already have the desired status or can't be marked. let articlesToMark = MarkStatusCommand.filteredArticles(initialArticles, statusKey, flag) @@ -30,8 +42,9 @@ final class MarkStatusCommand: UndoableCommand { completion?() return nil } - self.articles = Set(articlesToMark) + self.articles = articlesToMark + self.directlyMarked = directlyMarked self.flag = flag self.statusKey = statusKey self.undoManager = undoManager @@ -42,21 +55,39 @@ final class MarkStatusCommand: UndoableCommand { self.redoActionName = actionName } - convenience init?(initialArticles: [Article], markingRead: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) { - self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, undoManager: undoManager, completion: completion) + convenience init?(initialArticles: [Article], statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) { + self.init(initialArticles: Set(initialArticles), statusKey: .read, flag: flag, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion) } - convenience init?(initialArticles: [Article], markingStarred: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) { - self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, undoManager: undoManager, completion: completion) + convenience init?(initialArticles: Set
, markingRead: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) { + self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion) + } + + convenience init?(initialArticles: [Article], markingRead: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) { + self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion) + } + + convenience init?(initialArticles: Set
, markingStarred: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) { + self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion) + } + + convenience init?(initialArticles: [Article], markingStarred: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) { + self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion) } func perform() { mark(statusKey, flag) + if directlyMarked { + markStatusCommandDidDirectMarking() + } registerUndo() } func undo() { mark(statusKey, !flag) + if directlyMarked { + markStatusCommandDidUndoDirectMarking() + } registerRedo() } } @@ -67,6 +98,18 @@ private extension MarkStatusCommand { markArticles(articles, statusKey: statusKey, flag: flag, completion: completion) completion = nil } + + func markStatusCommandDidDirectMarking() { + NotificationCenter.default.post(name: .MarkStatusCommandDidDirectMarking, object: self, userInfo: [Account.UserInfoKey.articles: articles, + Account.UserInfoKey.statusKey: statusKey, + Account.UserInfoKey.statusFlag: flag]) + } + + func markStatusCommandDidUndoDirectMarking() { + NotificationCenter.default.post(name: .MarkStatusCommandDidUndoDirectMarking, object: self, userInfo: [Account.UserInfoKey.articles: articles, + Account.UserInfoKey.statusKey: statusKey, + Account.UserInfoKey.statusFlag: flag]) + } static private let markReadActionName = NSLocalizedString("Mark Read", comment: "command") static private let markUnreadActionName = NSLocalizedString("Mark Unread", comment: "command") @@ -83,7 +126,7 @@ private extension MarkStatusCommand { } } - static func filteredArticles(_ articles: [Article], _ statusKey: ArticleStatus.Key, _ flag: Bool) -> [Article] { + static func filteredArticles(_ articles: Set
, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set
{ return articles.filter{ article in guard article.status.boolStatus(forKey: statusKey) != flag else { return false } @@ -93,4 +136,5 @@ private extension MarkStatusCommand { } } + } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index ba3c3a737..89e028c85 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -110,6 +110,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging { } } + private var directlyMarkedAsUnreadArticles = Set
() + var prefersStatusBarHidden = false private let treeControllerDelegate = WebFeedTreeControllerDelegate() @@ -330,6 +332,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging { NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(importDownloadedTheme(_:)), name: .didEndDownloadingTheme, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidDirectMarking(_:)), name: .MarkStatusCommandDidDirectMarking, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidUndoDirectMarking(_:)), name: .MarkStatusCommandDidUndoDirectMarking, object: nil) } func restoreWindowState(_ activity: NSUserActivity?) { @@ -546,6 +550,28 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging { } } + @objc func markStatusCommandDidDirectMarking(_ note: Notification) { + guard let userInfo = note.userInfo, + let articles = userInfo[Account.UserInfoKey.articles] as? Set
, + let statusKey = userInfo[Account.UserInfoKey.statusKey] as? ArticleStatus.Key, + let flag = userInfo[Account.UserInfoKey.statusFlag] as? Bool else { return } + + if statusKey == .read && flag == false { + directlyMarkedAsUnreadArticles.formUnion(articles) + } + } + + @objc func markStatusCommandDidUndoDirectMarking(_ note: Notification) { + guard let userInfo = note.userInfo, + let articles = userInfo[Account.UserInfoKey.articles] as? Set
, + let statusKey = userInfo[Account.UserInfoKey.statusKey] as? ArticleStatus.Key, + let flag = userInfo[Account.UserInfoKey.statusFlag] as? Bool else { return } + + if statusKey == .read && flag == false { + directlyMarkedAsUnreadArticles.subtract(articles) + } + } + // MARK: API func suspend() { @@ -996,7 +1022,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging { } func markAllAsRead(_ articles: [Article], completion: (() -> Void)? = nil) { - markArticlesWithUndo(articles, statusKey: .read, flag: true, completion: completion) + var markableArticles = Set(articles) + markableArticles.subtract(directlyMarkedAsUnreadArticles) + markArticlesWithUndo(markableArticles, statusKey: .read, flag: true, directlyMarked: false, completion: completion) } func markAllAsReadInTimeline(completion: (() -> Void)? = nil) { @@ -1044,13 +1072,13 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging { func markAsReadForCurrentArticle() { if let article = currentArticle { - markArticlesWithUndo([article], statusKey: .read, flag: true) + markArticlesWithUndo([article], statusKey: .read, flag: true, directlyMarked: true) } } func markAsUnreadForCurrentArticle() { if let article = currentArticle { - markArticlesWithUndo([article], statusKey: .read, flag: false) + markArticlesWithUndo([article], statusKey: .read, flag: false, directlyMarked: true) } } @@ -1062,7 +1090,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging { func toggleRead(_ article: Article) { guard !article.status.read || article.isAvailableToMarkUnread else { return } - markArticlesWithUndo([article], statusKey: .read, flag: !article.status.read) + markArticlesWithUndo([article], statusKey: .read, flag: !article.status.read, directlyMarked: true) } func toggleStarredForCurrentArticle() { @@ -1072,7 +1100,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging { } func toggleStar(_ article: Article) { - markArticlesWithUndo([article], statusKey: .starred, flag: !article.status.starred) + markArticlesWithUndo([article], statusKey: .starred, flag: !article.status.starred, directlyMarked: true) } func timelineFeedIsEqualTo(_ feed: WebFeed) -> Bool { @@ -1415,9 +1443,18 @@ private extension SceneCoordinator { navController.toolbar.tintColor = AppAssets.primaryAccentColor } - func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) { + func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool, completion: (() -> Void)? = nil) { + markArticlesWithUndo(Set(articles), statusKey: statusKey, flag: flag, directlyMarked: directlyMarked, completion: completion) + } + + func markArticlesWithUndo(_ articles: Set
, statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool, completion: (() -> Void)? = nil) { guard let undoManager = undoManager, - let markReadCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager, completion: completion) else { + let markReadCommand = MarkStatusCommand(initialArticles: articles, + statusKey: statusKey, + flag: flag, + directlyMarked: directlyMarked, + undoManager: undoManager, + completion: completion) else { completion?() return } @@ -1935,6 +1972,7 @@ private extension SceneCoordinator { func emptyTheTimeline() { if !articles.isEmpty { + directlyMarkedAsUnreadArticles = Set
() replaceArticles(with: Set
(), animated: false) } }