Working implementations

This commit is contained in:
Bryan Culver
2022-12-07 00:30:19 -05:00
parent 002f3e87fd
commit 35a6cf551b
16 changed files with 319 additions and 103 deletions

View File

@@ -653,6 +653,22 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
fetchUnreadCounts(for: webFeeds, completion: completion)
}
public func fetchUnreadArticlesBetween(limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
return try fetchUnreadArticlesBetween(forContainer: self, limit: limit, before: before, after: after)
}
public func fetchUnreadArticlesBetween(folder: Folder, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
return try fetchUnreadArticlesBetween(forContainer: folder, limit: limit, before: before, after: after)
}
public func fetchUnreadArticlesBetween(webFeeds: Set<WebFeed>, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
return try fetchUnreadArticlesBetween(feeds: webFeeds, limit: limit, before: before, after: after)
}
public func fetchArticlesBetween(articleIDs: Set<String>, before: Date?, after: Date?) throws -> Set<Article> {
return try database.fetchArticlesBetween(articleIDs: articleIDs, before: before, after: after)
}
public func fetchArticles(_ fetchType: FetchType) throws -> Set<Article> {
switch fetchType {
case .starred(let limit):
@@ -1041,10 +1057,6 @@ private extension Account {
return try fetchUnreadArticles(forContainer: self, limit: limit)
}
func fetchUnreadArticlesBetween(limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
return try fetchUnreadArticlesBetween(forContainer: self, limit: limit, before: before, after: after)
}
func fetchUnreadArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchUnreadArticlesAsync(forContainer: self, limit: limit, completion)
}
@@ -1157,13 +1169,11 @@ private extension Account {
func fetchUnreadArticlesBetween(forContainer container: Container, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
let feeds = container.flattenedWebFeeds()
let articles = try database.fetchUnreadArticlesBetween(feeds.webFeedIDs(), limit, before, after)
// We don't validate limit queries because they, by definition, won't correctly match the
// complete unread state for the given container.
if limit == nil {
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
}
return articles
}
func fetchUnreadArticlesBetween(feeds: Set<WebFeed>, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
let articles = try database.fetchUnreadArticlesBetween(feeds.webFeedIDs(), limit, before, after)
return articles
}

View File

@@ -396,6 +396,16 @@ public final class AccountManager: UnreadCountProvider {
}
}
}
public func fetchUnreadArticlesBetween(limit: Int? = nil, before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
precondition(Thread.isMainThread)
var articles = Set<Article>()
for account in activeAccounts {
articles.formUnion(try account.fetchUnreadArticlesBetween(limit: limit, before: before, after: after))
}
return articles
}
// MARK: - Fetching Article Counts

View File

@@ -37,9 +37,9 @@ extension WebFeed: ArticleFetcher {
public func fetchUnreadArticles() throws -> Set<Article> {
return try fetchArticles().unreadArticles()
}
public func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try fetchArticles().unreadArticlesBetween(before: before, after: after)
return try account?.fetchUnreadArticlesBetween(webFeeds: [self], limit: nil, before: before, after: after) ?? Set<Article>()
}
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
@@ -87,7 +87,11 @@ extension Folder: ArticleFetcher {
}
public func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try fetchArticles().unreadArticlesBetween(before: before, after: after)
guard let account = account else {
assertionFailure("Expected folder.account, but got nil.")
return Set<Article>()
}
return try account.fetchUnreadArticlesBetween(folder: self, limit: nil, before: before, after: after)
}
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {

View File

@@ -33,7 +33,7 @@ public struct SingleArticleFetcher: ArticleFetcher {
}
public func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try account.fetchArticles(.articleIDs(Set([articleID])))
return try account.fetchArticlesBetween(articleIDs: Set([articleID]), before: before, after: after)
}
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {

View File

@@ -79,19 +79,6 @@ public extension Set where Element == Article {
let articles = self.filter { !$0.status.read }
return Set(articles)
}
func unreadArticlesBetween(before: Date? = nil, after: Date? = nil) -> Set<Article> {
var articles = self.filter { !$0.status.read }
if before != nil {
// TODO: Address datePublished nil
articles = articles.filter { $0.datePublished != nil && $0.datePublished! <= before! }
}
if after != nil {
// TODO: Address datePublished nil
articles = articles.filter { $0.datePublished != nil && $0.datePublished! >= after! }
}
return Set(articles)
}
func contains(accountID: String, articleID: String) -> Bool {
return contains(where: { $0.accountID == accountID && $0.articleID == articleID})

View File

@@ -106,6 +106,10 @@ public final class ArticlesDatabase {
return try articlesTable.fetchUnreadArticles(webFeedIDs, limit)
}
public func fetchArticlesBetween(articleIDs: Set<String>, before: Date?, after: Date?) throws -> Set<Article> {
return try articlesTable.fetchArticlesBetween(articleIDs: articleIDs, before: before, after: after)
}
public func fetchUnreadArticlesBetween(_ webFeedIDs: Set<String>, _ limit: Int?, _ before: Date?, _ after: Date?) throws -> Set<Article> {
return try articlesTable.fetchUnreadArticlesBetween(webFeedIDs, limit, before, after)
}

View File

@@ -70,12 +70,16 @@ final class ArticlesTable: DatabaseTable {
return try fetchArticles{ self.fetchArticles(articleIDs: articleIDs, $0) }
}
func fetchArticlesBetween(articleIDs: Set<String>, before: Date?, after: Date?) throws -> Set<Article> {
return try fetchArticles{ self.fetchArticlesBetween(articleIDs: articleIDs, before: before, after: after, $0) }
}
func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
return fetchArticlesAsync({ self.fetchArticles(articleIDs: articleIDs, $0) }, completion)
}
// MARK: - Fetching Unread Articles
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, limit, $0) }
}
@@ -854,15 +858,22 @@ private extension ArticlesTable {
if webFeedIDs.isEmpty {
return Set<Article>()
}
let parameters = webFeedIDs.map { $0 as AnyObject }
var parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
var whereClause = "feedID in \(placeholders) and read=0"
if let before = before {
whereClause.append(" and (datePublished < ? or (datePublished is null and dateArrived < ?))")
parameters = parameters + [before as AnyObject, before as AnyObject]
}
if let after = after {
whereClause.append(" and (datePublished > ? or (datePublished is null and dateArrived > ?))")
parameters = parameters + [after as AnyObject, after as AnyObject]
}
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
}
if let before = before {
whereClause.append(" and dateArrived <= \(before)")
}
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
@@ -880,6 +891,26 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchArticlesBetween(articleIDs: Set<String>, before: Date?, after: Date?, _ database: FMDatabase) -> Set<Article> {
if articleIDs.isEmpty {
return Set<Article>()
}
var parameters = articleIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))!
var whereClause = "articleID in \(placeholders)"
if let before = before {
whereClause.append(" and (datePublished < ? or (datePublished is null and dateArrived < ?))")
parameters = parameters + [before as AnyObject, before as AnyObject]
}
if let after = after {
whereClause.append(" and (datePublished > ? or (datePublished is null and dateArrived > ?))")
parameters = parameters + [after as AnyObject, after as AnyObject]
}
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and (datePublished > ? || (datePublished is null and dateArrived > ?)
//

View File

@@ -166,41 +166,6 @@ final class StatusesTable: DatabaseTable {
}
}
func fetchArticleIDsArrivedBetweenDates(beforeDate: Date?, afterDate: Date?, completion: @escaping ArticleIDsCompletionBlock) {
queue.runInDatabase { databaseResult in
var error: DatabaseError?
var articleIDs = Set<String>()
var sql = "select artcileID from statuses s"
func makeDatabaseCall(_ database: FMDatabase) {
let sql = "select articleID from statuses s where (starred=1 or dateArrived>?) and not exists (select 1 from articles a where a.articleID = s.articleID);"
if let resultSet = database.executeQuery(sql, withArgumentsIn: [cutoffDate]) {
articleIDs = resultSet.mapToSet(self.articleIDWithRow)
}
}
switch databaseResult {
case .success(let database):
makeDatabaseCall(database)
case .failure(let databaseError):
error = databaseError
}
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
}
else {
DispatchQueue.main.async {
completion(.success(articleIDs))
}
}
}
}
func fetchArticleIDs(_ sql: String) throws -> Set<String> {
var error: DatabaseError?
var articleIDs = Set<String>()

View File

@@ -84,6 +84,56 @@ extension SidebarViewController {
runCommand(markReadCommand)
}
@objc func markObjectsReadOlderThanOneDayFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .day, value: -1, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanTwoDaysFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .day, value: -2, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanThreeDaysFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .year, value: -3, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanOneWeekFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanTwoWeeksFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .weekOfYear, value: -2, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanOneMonthFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .month, value: -1, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanOneYearFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .year, value: -1, to: Date()), after: nil, sender: sender)
}
func markObjectsReadBetweenDatesFromContextualMenu(before: Date?, after: Date?, sender: Any?) {
guard let menuItem = sender as? NSMenuItem, let objects = menuItem.representedObject as? [Any] else {
return
}
var markableArticles = unreadArticlesBetween(for: objects, before: before, after: after)
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)
}
@objc func deleteFromContextualMenu(_ sender: Any?) {
guard let menuItem = sender as? NSMenuItem, let objects = menuItem.representedObject as? [AnyObject] else {
return
@@ -219,13 +269,13 @@ private extension SidebarViewController {
let menu = NSMenu(title: "")
if webFeed.unreadCount > 0 {
// menu.addItem(markAllReadMenuItem([webFeed]))
let catchUpMenuItem = catchUpMenuItem([webFeed])
menu.addItem(markAllReadMenuItem([webFeed]))
let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark Older Than as Read...", comment: "Command Submenu"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu([webFeed])
menu.addItem(catchUpMenuItem)
menu.setSubmenu(catchUpSubMenu, for: catchUpMenuItem)
menu.addItem(NSMenuItem.separator())
}
@@ -282,6 +332,11 @@ private extension SidebarViewController {
if folder.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([folder]))
let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark Older Than as Read...", comment: "Command Submenu"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu([folder])
menu.addItem(catchUpMenuItem)
menu.setSubmenu(catchUpSubMenu, for: catchUpMenuItem)
menu.addItem(NSMenuItem.separator())
}
@@ -297,6 +352,11 @@ private extension SidebarViewController {
if smartFeed.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([smartFeed]))
let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark Older Than as Read...", comment: "Command Submenu"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu([smartFeed])
menu.addItem(catchUpMenuItem)
menu.setSubmenu(catchUpSubMenu, for: catchUpMenuItem)
}
return menu.numberOfItems > 0 ? menu : nil
}
@@ -307,6 +367,11 @@ private extension SidebarViewController {
if anyObjectInArrayHasNonZeroUnreadCount(objects) {
menu.addItem(markAllReadMenuItem(objects))
let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark Older Than as Read...", comment: "Command Submenu"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu(objects)
menu.addItem(catchUpMenuItem)
menu.setSubmenu(catchUpSubMenu, for: catchUpMenuItem)
}
if allObjectsAreFeedsAndOrFolders(objects) {
@@ -321,27 +386,20 @@ private extension SidebarViewController {
return menuItem(NSLocalizedString("Mark All as Read", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects)
}
func catchUpSubMenu(_ objects: [Any]) -> NSMenu {
let menu = NSMenu(title: "Catch up to articles older than...")
menu.addItem(menuItem(NSLocalizedString("All", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("Older than 1 day", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("Older than 2 days", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("Older than 3 days", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("Older than 1 week", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("Older than 2 weeks", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("Older than 1 month", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("Older than 1 year", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("1 day", comment: "Command"), #selector(markObjectsReadOlderThanOneDayFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("2 days", comment: "Command"), #selector(markObjectsReadOlderThanTwoDaysFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("3 days", comment: "Command"), #selector(markObjectsReadOlderThanThreeDaysFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("1 week", comment: "Command"), #selector(markObjectsReadOlderThanOneWeekFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("2 weeks", comment: "Command"), #selector(markObjectsReadOlderThanTwoWeeksFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("1 month", comment: "Command"), #selector(markObjectsReadOlderThanOneMonthFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("1 year", comment: "Command"), #selector(markObjectsReadOlderThanOneYearFromContextualMenu(_:)), objects))
return menu
}
func catchUpMenuItem(_ objects: [Any]) -> NSMenuItem {
return menuItem(NSLocalizedString("Mark as Read...", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects)
}
func deleteMenuItem(_ objects: [Any]) -> NSMenuItem {
@@ -400,5 +458,18 @@ private extension SidebarViewController {
}
return articles
}
func unreadArticlesBetween(for objects: [Any], before: Date?, after: Date?) -> Set<Article> {
var articles = Set<Article>()
for object in objects {
if let articleFetcher = object as? ArticleFetcher {
if let unreadArticles = try? articleFetcher.fetchUnreadArticlesBetween(before: before, after: after) {
articles.formUnion(unreadArticles)
}
}
}
return articles
}
}

View File

@@ -35,5 +35,10 @@ struct SearchFeedDelegate: SmartFeedDelegate {
func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) {
// TODO: after 5.0
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
// TODO FILTER BY SEARCH
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
}
}

View File

@@ -35,4 +35,9 @@ struct SearchTimelineFeedDelegate: SmartFeedDelegate {
func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) {
// TODO: after 5.0
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
// TODO FILTER BY SEARCH
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
}
}

View File

@@ -46,7 +46,7 @@ final class SmartFeed: PseudoFeed {
}
#endif
private let delegate: SmartFeedDelegate
public let delegate: SmartFeedDelegate
private var unreadCounts = [String: Int]()
init(delegate: SmartFeedDelegate) {

View File

@@ -32,7 +32,7 @@ extension SmartFeedDelegate {
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try fetchArticles().unreadArticlesBetween(before: before, after: after)
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
}
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {

View File

@@ -29,4 +29,9 @@ struct StarredFeedDelegate: SmartFeedDelegate {
func fetchUnreadCount(for account: Account, completion: @escaping SingleUnreadCountCompletionBlock) {
account.fetchUnreadCountForStarredArticles(completion)
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
// TODO FILTER BY STARRED
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
}
}

View File

@@ -73,13 +73,13 @@ extension UnreadFeed: ArticleFetcher {
func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
fetchUnreadArticlesAsync(completion)
}
func fetchUnreadArticles() throws -> Set<Article> {
return try AccountManager.shared.fetchArticles(fetchType)
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try AccountManager.shared.fetchArticles(fetchType)
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
}
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {

View File

@@ -693,6 +693,9 @@ extension MasterFeedViewController: UIContextMenuInteractionDelegate {
menuElements.append(UIMenu(title: "", options: .displayInline, children: [markAllAction]))
}
if let catchUpAction = self.catchUpActionMenu(account: account, contentView: interaction.view) {
menuElements.append(catchUpAction)
}
menuElements.append(UIMenu(title: "", options: .displayInline, children: [self.deactivateAccountAction(account: account)]))
return UIMenu(title: "", children: menuElements)
@@ -923,7 +926,7 @@ private extension MasterFeedViewController {
menuElements.append(UIMenu(title: "", options: .displayInline, children: [markAllAction]))
}
if let catchUpAction = self.catchUpActionMenu(indexPath: indexPath) {
menuElements.append(catchUpAction)
}
@@ -953,6 +956,10 @@ private extension MasterFeedViewController {
if let markAllAction = self.markAllAsReadAction(indexPath: indexPath) {
menuElements.append(UIMenu(title: "", options: .displayInline, children: [markAllAction]))
}
if let catchUpAction = self.catchUpActionMenu(indexPath: indexPath) {
menuElements.append(catchUpAction)
}
menuElements.append(UIMenu(title: "",
options: .displayInline,
@@ -966,13 +973,22 @@ private extension MasterFeedViewController {
})
}
func makePseudoFeedContextMenu(indexPath: IndexPath) -> UIContextMenuConfiguration? {
guard let markAllAction = self.markAllAsReadAction(indexPath: indexPath) else {
return nil
}
func makePseudoFeedContextMenu(indexPath: IndexPath) -> UIContextMenuConfiguration {
return UIContextMenuConfiguration(identifier: MasterFeedRowIdentifier(indexPath: indexPath), previewProvider: nil, actionProvider: { [weak self] suggestedActions in
return UIContextMenuConfiguration(identifier: MasterFeedRowIdentifier(indexPath: indexPath), previewProvider: nil, actionProvider: { suggestedActions in
return UIMenu(title: "", children: [markAllAction])
guard let self = self else { return nil }
var menuElements = [UIMenuElement]()
if let markAllAction = self.markAllAsReadAction(indexPath: indexPath) {
menuElements.append(UIMenu(title: "", options: .displayInline, children: [markAllAction]))
}
if let catchUpAction = self.catchUpActionMenu(indexPath: indexPath) {
menuElements.append(catchUpAction)
}
return UIMenu(title: "", children: menuElements)
})
}
@@ -1162,7 +1178,14 @@ private extension MasterFeedViewController {
feed.unreadCount > 0 else {
return nil
}
// Doesn't make sense to mark articles newer than a day with catch up with first option being older than a day
if let maybeSmartFeed = feed as? SmartFeed {
if maybeSmartFeed.delegate is TodayFeedDelegate {
return nil
}
}
let title = NSLocalizedString("Mark Older Than as Read...", comment: "Command")
let oneDayAction = UIAction(title: "1 Day") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Day as Read", sourceType: contentView) { [weak self] in
@@ -1232,7 +1255,7 @@ private extension MasterFeedViewController {
return majorMenu
}
func getMarkOlderImageDirection() -> UIImage {
if AppDefaults.shared.timelineSortDirection == .orderedDescending {
return AppAssets.markBelowAsReadImage
@@ -1260,6 +1283,102 @@ private extension MasterFeedViewController {
return action
}
func catchUpActionMenu(account: Account, contentView: UIView?) -> UIMenu? {
guard account.unreadCount > 0, let contentView = contentView else {
return nil
}
let title = NSLocalizedString("Mark Older Than as Read...", comment: "Command")
let oneDayAction = UIAction(title: "1 Day") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Day as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .day, value: -1, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let twoDayAction = UIAction(title: "2 Days") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 2 Days as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .day, value: -2, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let threeDayAction = UIAction(title: "3 Days") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 3 Days as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .day, value: -3, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let oneWeekAction = UIAction(title: "1 Week") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Week as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let twoWeekAction = UIAction(title: "2 Weeks") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 2 Weeks as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .weekOfYear, value: -2, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let oneMonthAction = UIAction(title: "1 Month") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Month as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .month, value: -1, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let oneYearAction = UIAction(title: "1 Year") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Year as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .year, value: -1, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
var markActions = [UIAction]()
markActions.append(oneDayAction)
markActions.append(twoDayAction)
markActions.append(threeDayAction)
markActions.append(oneWeekAction)
markActions.append(twoWeekAction)
markActions.append(oneMonthAction)
markActions.append(oneYearAction)
let majorMenu = UIMenu(title: title, image: getMarkOlderImageDirection(), children: markActions)
return majorMenu
}
func rename(indexPath: IndexPath) {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { return }