Merge branch 'master' into accent-color-experimental

This commit is contained in:
Maurice Parker 2020-03-24 12:24:59 -05:00
commit c6bd60eb1e
17 changed files with 184 additions and 159 deletions

View File

@ -695,56 +695,49 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
completion(nil)
return
}
let group = DispatchGroup()
var possibleError: DatabaseError? = nil
var newArticles = Set<Article>()
var updatedArticles = Set<Article>()
for (webFeedID, items) in webFeedIDsAndItems {
group.enter()
database.update(webFeedID: webFeedID, items: items, defaultRead: defaultRead) { updateArticlesResult in
switch updateArticlesResult {
case .success(let newAndUpdatedArticles):
if let articles = newAndUpdatedArticles.newArticles {
newArticles.formUnion(articles)
}
if let articles = newAndUpdatedArticles.updatedArticles {
updatedArticles.formUnion(articles)
}
case .failure(let databaseError):
possibleError = databaseError
database.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead) { updateArticlesResult in
func sendNotificationAbout(newArticles: Set<Article>?, updatedArticles: Set<Article>?) {
var webFeeds = Set<WebFeed>()
if let newArticles = newArticles {
webFeeds.formUnion(Set(newArticles.compactMap { $0.webFeed }))
}
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
var userInfo = [String: Any]()
var webFeeds = Set(newArticles.compactMap { $0.webFeed })
webFeeds.formUnion(Set(updatedArticles.compactMap { $0.webFeed }))
if !newArticles.isEmpty {
self.updateUnreadCounts(for: webFeeds) {
NotificationCenter.default.post(name: .DownloadArticlesDidUpdateUnreadCounts, object: self, userInfo: nil)
if let updatedArticles = updatedArticles {
webFeeds.formUnion(Set(updatedArticles.compactMap { $0.webFeed }))
}
var shouldSendNotification = false
var userInfo = [String: Any]()
if let newArticles = newArticles, !newArticles.isEmpty {
shouldSendNotification = true
userInfo[UserInfoKey.newArticles] = newArticles
self.updateUnreadCounts(for: webFeeds) {
NotificationCenter.default.post(name: .DownloadArticlesDidUpdateUnreadCounts, object: self, userInfo: nil)
}
}
if let updatedArticles = updatedArticles, !updatedArticles.isEmpty {
shouldSendNotification = true
userInfo[UserInfoKey.updatedArticles] = updatedArticles
}
if shouldSendNotification {
userInfo[UserInfoKey.webFeeds] = webFeeds
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
}
userInfo[UserInfoKey.newArticles] = newArticles
}
if !updatedArticles.isEmpty {
userInfo[UserInfoKey.updatedArticles] = updatedArticles
switch updateArticlesResult {
case .success(let newAndUpdatedArticles):
sendNotificationAbout(newArticles: newAndUpdatedArticles.newArticles, updatedArticles: newAndUpdatedArticles.updatedArticles)
completion(nil)
case .failure(let databaseError):
completion(databaseError)
}
userInfo[UserInfoKey.webFeeds] = webFeeds
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
completion(possibleError)
}
}
@discardableResult

View File

@ -184,8 +184,8 @@ public final class ArticlesDatabase {
// MARK: - Saving and Updating Articles
/// Update articles and save new ones.
public func update(webFeedID: String, items: Set<ParsedItem>, defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
articlesTable.update(webFeedID, items, defaultRead, completion)
public func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
articlesTable.update(webFeedIDsAndItems, defaultRead, completion)
}
// MARK: - Status

View File

@ -169,8 +169,8 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Updating
func update(_ webFeedID: String, _ items: Set<ParsedItem>, _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
if items.isEmpty {
func update(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
if webFeedIDsAndItems.isEmpty {
callUpdateArticlesCompletionBlock(nil, nil, completion)
return
}
@ -187,11 +187,15 @@ final class ArticlesTable: DatabaseTable {
self.queue.runInTransaction { (databaseResult) in
func makeDatabaseCalls(_ database: FMDatabase) {
let articleIDs = items.articleIDs()
var articleIDs = Set<String>()
for (_, parsedItems) in webFeedIDsAndItems {
articleIDs.formUnion(parsedItems.articleIDs())
}
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
assert(statusesDictionary.count == articleIDs.count)
let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedID, items, self.accountID, statusesDictionary) //2
let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2
if allIncomingArticles.isEmpty {
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
return

View File

@ -112,9 +112,16 @@ extension Article {
// return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
// }
static func articlesWithWebFeedIDsAndItems(_ webFeedID: String, _ items: Set<ParsedItem>, _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
static func articlesWithWebFeedIDsAndItems(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
let feedArticles = Set(items.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: statusesDictionary[$0.articleID]!) })
var feedArticles = Set<Article>()
for (webFeedID, parsedItems) in webFeedIDsAndItems {
for parsedItem in parsedItems {
let status = statusesDictionary[parsedItem.articleID]!
let article = Article(parsedItem: parsedItem, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: status)
feedArticles.insert(article)
}
}
return feedArticles
}
}

View File

@ -6,6 +6,7 @@
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import RSCore
#if os(macOS)
import AppKit
#else
@ -18,13 +19,8 @@ extension RSImage {
static let maxIconSize = 48
static func scaledForIcon(_ data: Data, imageResultBlock: @escaping (RSImage?) -> Void) {
DispatchQueue.global(qos: .default).async {
let image = RSImage.scaledForIcon(data)
DispatchQueue.main.async {
imageResultBlock(image)
}
}
static func scaledForIcon(_ data: Data, imageResultBlock: @escaping ImageResultBlock) {
IconScalerQueue.shared.scaledForIcon(data, imageResultBlock)
}
static func scaledForIcon(_ data: Data) -> RSImage? {
@ -41,3 +37,26 @@ extension RSImage {
#endif
}
}
// MARK: - IconScalerQueue
private class IconScalerQueue {
static let shared = IconScalerQueue()
private let queue: DispatchQueue = {
let q = DispatchQueue(label: "IconScaler", attributes: .initiallyInactive)
q.setTarget(queue: DispatchQueue.global(qos: .default))
q.activate()
return q
}()
func scaledForIcon(_ data: Data, _ imageResultBlock: @escaping ImageResultBlock) {
queue.async {
let image = RSImage.scaledForIcon(data)
DispatchQueue.main.async {
imageResultBlock(image)
}
}
}
}

View File

@ -178,11 +178,6 @@ final class FaviconDownloader {
remainingFaviconURLs[homePageURL] = nil
if self.homePageToFaviconURLCache[homePageURL] == nil {
self.homePageToFaviconURLCache[homePageURL] = singleFaviconDownloader.faviconURL
self.homePageToFaviconURLCacheDirty = true
}
postFaviconDidBecomeAvailableNotification(singleFaviconDownloader.faviconURL)
}
@ -232,8 +227,22 @@ private extension FaviconDownloader {
func faviconDownloader(withURL faviconURL: String, homePageURL: String?) -> SingleFaviconDownloader {
var firstTimeSeeingHomepageURL = false
if let homePageURL = homePageURL, self.homePageToFaviconURLCache[homePageURL] == nil {
self.homePageToFaviconURLCache[homePageURL] = faviconURL
self.homePageToFaviconURLCacheDirty = true
firstTimeSeeingHomepageURL = true
}
if let downloader = singleFaviconDownloaderCache[faviconURL] {
downloader.downloadFaviconIfNeeded()
if firstTimeSeeingHomepageURL && !downloader.downloadFaviconIfNeeded() {
// This is to handle the scenario where we have different homepages, but the same favicon.
// This happens for Twitter and probably other sites like Blogger. Because the favicon
// is cached, we wouldn't send out a notification that it is now available unless we send
// it here.
postFaviconDidBecomeAvailableNotification(faviconURL)
}
return downloader
}

View File

@ -48,21 +48,23 @@ final class SingleFaviconDownloader {
findFavicon()
}
func downloadFaviconIfNeeded() {
func downloadFaviconIfNeeded() -> Bool {
// If we dont have an image, and lastDownloadAttemptDate is a while ago, try again.
if let _ = iconImage {
return
return false
}
let retryInterval: TimeInterval = 30 * 60 // 30 minutes
if Date().timeIntervalSince(lastDownloadAttemptDate) < retryInterval {
return
return false
}
lastDownloadAttemptDate = Date()
findFavicon()
return true
}
}

View File

@ -139,13 +139,19 @@ private extension KeyboardManager {
keys.append(KeyboardManager.createKeyCommand(title: goToStarredTitle, action: "goToStarred:", input: "3", modifiers: [.command]))
let articleSearchTitle = NSLocalizedString("Article Search", comment: "Article Search")
keys.append(KeyboardManager.createKeyCommand(title: articleSearchTitle, action: "articleSearch:", input: "f", modifiers: [.command, .shift]))
keys.append(KeyboardManager.createKeyCommand(title: articleSearchTitle, action: "articleSearch:", input: "f", modifiers: [.command, .alternate]))
let markAllAsReadTitle = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
keys.append(KeyboardManager.createKeyCommand(title: markAllAsReadTitle, action: "markAllAsRead:", input: "k", modifiers: [.command]))
let cleanUp = NSLocalizedString("Clean Up", comment: "Clean Up")
keys.append(KeyboardManager.createKeyCommand(title: cleanUp, action: "cleanUp:", input: "h", modifiers: [.command, .shift]))
keys.append(KeyboardManager.createKeyCommand(title: cleanUp, action: "cleanUp:", input: "'", modifiers: [.command]))
let toggleReadFeedsFilter = NSLocalizedString("Toggle Read Feeds Filter", comment: "Toggle Read Feeds Filter")
keys.append(KeyboardManager.createKeyCommand(title: toggleReadFeedsFilter, action: "toggleReadFeedsFilter:", input: "f", modifiers: [.command, .shift]))
let toggleReadArticlesFilter = NSLocalizedString("Toggle Read Articles Filter", comment: "Toggle Read Articles Filter")
keys.append(KeyboardManager.createKeyCommand(title: toggleReadArticlesFilter, action: "toggleReadArticlesFilter:", input: "h", modifiers: [.command, .shift]))
return keys
}

View File

@ -378,13 +378,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
}
@IBAction func toggleFilter(_ sender: Any) {
if coordinator.isReadFeedsFiltered {
setFilterButtonToInactive()
coordinator.showAllFeeds()
} else {
setFilterButtonToActive()
coordinator.hideReadFeeds()
}
coordinator.toggleReadFeedsFilter()
}
@IBAction func add(_ sender: UIBarButtonItem) {
@ -509,6 +503,16 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
}
}
func updateUI() {
if coordinator.isReadFeedsFiltered {
setFilterButtonToActive()
} else {
setFilterButtonToInactive()
}
refreshProgressView?.updateRefreshLabel()
addNewItemButton?.isEnabled = !AccountManager.shared.activeAccounts.isEmpty
}
func focus() {
becomeFirstResponder()
}
@ -580,16 +584,6 @@ private extension MasterFeedViewController {
toolbarItems?.insert(refreshProgressItemButton, at: 2)
}
func updateUI() {
if coordinator.isReadFeedsFiltered {
setFilterButtonToActive()
} else {
setFilterButtonToInactive()
}
refreshProgressView?.updateRefreshLabel()
addNewItemButton?.isEnabled = !AccountManager.shared.activeAccounts.isEmpty
}
func setFilterButtonToActive() {
filterButton?.image = AppAssets.filterActiveImage
filterButton?.accLabelText = NSLocalizedString("Selected - Filter Read Feeds", comment: "Selected - Filter Read Feeds")

View File

@ -19,7 +19,6 @@ struct MasterTimelineAccessibilityCellLayout: MasterTimelineCellLayout {
let summaryRect: CGRect
let feedNameRect: CGRect
let dateRect: CGRect
let separatorInsets: UIEdgeInsets
init(width: CGFloat, insets: UIEdgeInsets, cellData: MasterTimelineCellData) {
@ -34,9 +33,6 @@ struct MasterTimelineAccessibilityCellLayout: MasterTimelineCellLayout {
// Start the point at the beginning position of the main block
currentPoint.x += MasterTimelineDefaultCellLayout.unreadCircleDimension + MasterTimelineDefaultCellLayout.unreadCircleMarginRight
// Separator Insets
self.separatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
// Icon Image
if cellData.showIcon {
self.iconImageRect = MasterTimelineAccessibilityCellLayout.rectForIconView(currentPoint, iconSize: cellData.iconSize)

View File

@ -18,7 +18,6 @@ protocol MasterTimelineCellLayout {
var summaryRect: CGRect {get}
var feedNameRect: CGRect {get}
var dateRect: CGRect {get}
var separatorInsets: UIEdgeInsets {get}
}

View File

@ -51,7 +51,6 @@ struct MasterTimelineDefaultCellLayout: MasterTimelineCellLayout {
let summaryRect: CGRect
let feedNameRect: CGRect
let dateRect: CGRect
let separatorInsets: UIEdgeInsets
init(width: CGFloat, insets: UIEdgeInsets, cellData: MasterTimelineCellData) {
@ -66,9 +65,6 @@ struct MasterTimelineDefaultCellLayout: MasterTimelineCellLayout {
// Start the point at the beginning position of the main block
currentPoint.x += MasterTimelineDefaultCellLayout.unreadCircleDimension + MasterTimelineDefaultCellLayout.unreadCircleMarginRight
// Separator Insets
self.separatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
// Icon Image
if cellData.showIcon {
self.iconImageRect = MasterTimelineDefaultCellLayout.rectForIconView(currentPoint, iconSize: cellData.iconSize)

View File

@ -79,7 +79,7 @@ class MasterTimelineTableViewCell: VibrantTableViewCell {
feedNameView.setFrameIfNotEqual(layout.feedNameRect)
dateView.setFrameIfNotEqual(layout.dateRect)
separatorInset = layout.separatorInsets
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func setIconImage(_ image: IconImage) {

View File

@ -70,7 +70,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
tableView.dataSource = dataSource
numberOfTextLines = AppDefaults.timelineNumberOfLines
iconSize = AppDefaults.timelineIconSize
tableView.rowHeight = calculateEstimatedRowHeight(forId: PrototypeFeedContent.feedId, withTitle: PrototypeFeedContent.longTitle, andFeed: PrototypeFeedContent.feedname)
resetEstimatedRowHeight()
if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView {
navigationItem.titleView = titleView
@ -111,13 +111,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
// MARK: Actions
@IBAction func toggleFilter(_ sender: Any) {
if coordinator.isReadArticlesFiltered {
setFilterButtonToInactive()
coordinator.showAllArticles()
} else {
setFilterButtonToActive()
coordinator.hideReadArticles()
}
coordinator.toggleReadArticlesFilter()
}
@IBAction func markAllAsRead(_ sender: Any) {
@ -443,7 +437,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
if numberOfTextLines != AppDefaults.timelineNumberOfLines || iconSize != AppDefaults.timelineIconSize {
numberOfTextLines = AppDefaults.timelineNumberOfLines
iconSize = AppDefaults.timelineIconSize
tableView.rowHeight = calculateEstimatedRowHeight(forId: PrototypeFeedContent.feedId, withTitle: PrototypeFeedContent.longTitle, andFeed: PrototypeFeedContent.feedname)
resetEstimatedRowHeight()
reloadAllVisibleCells()
}
}
@ -487,21 +481,26 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
// MARK: Cell Configuring
private func calculateEstimatedRowHeight(forId prototypeID: String, withTitle title: String, andFeed feedName: String) -> CGFloat {
private func resetEstimatedRowHeight() {
let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
let prototypeID = "prototype"
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: title, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status)
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status)
let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: feedName, iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize)
let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize)
if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory {
let layout = MasterTimelineAccessibilityCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData)
return layout.height
tableView.estimatedRowHeight = layout.height
} else {
let layout = MasterTimelineDefaultCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData)
return layout.height
tableView.estimatedRowHeight = layout.height
}
}
}
// MARK: Searching
@ -584,11 +583,13 @@ private extension MasterTimelineViewController {
}
if coordinator.isReadArticlesFiltered {
setFilterButtonToActive()
filterButton?.image = AppAssets.filterActiveImage
filterButton?.accLabelText = NSLocalizedString("Selected - Filter Read Articles", comment: "Selected - Filter Read Articles")
} else {
setFilterButtonToInactive()
filterButton?.image = AppAssets.filterInactiveImage
filterButton?.accLabelText = NSLocalizedString("Filter Read Articles", comment: "Filter Read Articles")
}
tableView.selectRow(at: nil, animated: false, scrollPosition: .top)
if resetScroll && dataSource.snapshot().itemIdentifiers(inSection: 0).count > 0 {
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
@ -598,16 +599,6 @@ private extension MasterTimelineViewController {
}
func setFilterButtonToActive() {
filterButton?.image = AppAssets.filterActiveImage
filterButton?.accLabelText = NSLocalizedString("Selected - Filter Read Articles", comment: "Selected - Filter Read Articles")
}
func setFilterButtonToInactive() {
filterButton?.image = AppAssets.filterInactiveImage
filterButton?.accLabelText = NSLocalizedString("Filter Read Articles", comment: "Filter Read Articles")
}
func updateToolbar() {
markAllAsReadButton.isEnabled = coordinator.isTimelineUnreadAvailable
firstUnreadButton.isEnabled = coordinator.isTimelineUnreadAvailable
@ -625,12 +616,16 @@ private extension MasterTimelineViewController {
}
func applyChanges(animated: Bool, completion: (() -> Void)? = nil) {
if coordinator.articles.count == 0 {
tableView.rowHeight = tableView.estimatedRowHeight
} else {
tableView.rowHeight = UITableView.automaticDimension
}
var snapshot = NSDiffableDataSourceSnapshot<Int, Article>()
snapshot.appendSections([0])
snapshot.appendItems(coordinator.articles, toSection: 0)
if coordinator.articles.count == 0 {
tableView.rowHeight = calculateEstimatedRowHeight(forId: PrototypeFeedContent.feedId, withTitle: PrototypeFeedContent.longTitle, andFeed: PrototypeFeedContent.feedname)
}
dataSource.apply(snapshot, animatingDifferences: animated) { [weak self] in
self?.restoreSelectionIfNecessary(adjustScroll: false)
completion?()
@ -901,10 +896,3 @@ private extension MasterTimelineViewController {
}
}
fileprivate struct PrototypeFeedContent {
static let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
static let shortTitle = "prototype"
static let feedId = "feedId"
static let feedname = "prototype"
}

View File

@ -169,6 +169,10 @@ code, pre {
border: 1px solid var(--secondary-accent-color);
font-size: inherit;
}
.nnw-overflow table table {
margin-bottom: 0;
border: none;
}
.nnw-overflow td, .nnw-overflow th {
-webkit-hyphens: none;
word-break: normal;

View File

@ -94,6 +94,14 @@ class RootSplitViewController: UISplitViewController {
coordinator.cleanUp()
}
@objc func toggleReadFeedsFilter(_ sender: Any?) {
coordinator.toggleReadFeedsFilter()
}
@objc func toggleReadArticlesFilter(_ sender: Any?) {
coordinator.toggleReadArticlesFilter()
}
@objc func refresh(_ sender: Any?) {
appDelegate.manualRefresh(errorHandler: ErrorHandler.present(self))
}

View File

@ -541,7 +541,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
@objc func downloadArticlesDidUpdateUnreadCounts(_ note: Notification) {
rebuildBackingStores()
rebuildBackingStoresWithMerge()
}
@objc func accountDidDownloadArticles(_ note: Notification) {
@ -581,6 +581,30 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
}
func toggleReadFeedsFilter() {
if isReadFeedsFiltered {
treeControllerDelegate.isReadFiltered = false
} else {
treeControllerDelegate.isReadFiltered = true
}
rebuildBackingStores()
masterFeedViewController?.updateUI()
}
func toggleReadArticlesFilter() {
guard let feedID = timelineFeed?.feedID else {
return
}
if isReadArticlesFiltered {
readFilterEnabledTable[feedID] = false
} else {
readFilterEnabledTable[feedID] = true
}
refreshTimeline(resetScroll: false)
}
func shadowNodesFor(section: Int) -> [Node] {
return shadowTable[section]
}
@ -617,30 +641,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
}
func showAllFeeds() {
treeControllerDelegate.isReadFiltered = false
rebuildBackingStores()
}
func hideReadFeeds() {
treeControllerDelegate.isReadFiltered = true
rebuildBackingStores()
}
func showAllArticles() {
if let feedID = timelineFeed?.feedID {
readFilterEnabledTable[feedID] = false
}
refreshTimeline(resetScroll: false)
}
func hideReadArticles() {
if let feedID = timelineFeed?.feedID {
readFilterEnabledTable[feedID] = true
}
refreshTimeline(resetScroll: false)
}
func isExpanded(_ containerIdentifiable: ContainerIdentifiable) -> Bool {
if let containerID = containerIdentifiable.containerID {
return expandedTable.contains(containerID)