mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Stashing
This commit is contained in:
@@ -1043,6 +1043,10 @@ 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)
|
||||
}
|
||||
@@ -1152,6 +1156,19 @@ private extension Account {
|
||||
return articles
|
||||
}
|
||||
|
||||
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 fetchUnreadArticlesAsync(forContainer container: Container, limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
let webFeeds = container.flattenedWebFeeds()
|
||||
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs(), limit) { [weak self] (articleSetResult) in
|
||||
|
||||
@@ -15,6 +15,7 @@ public protocol ArticleFetcher {
|
||||
func fetchArticles() throws -> Set<Article>
|
||||
func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock)
|
||||
func fetchUnreadArticles() throws -> Set<Article>
|
||||
func fetchUnreadArticlesBetween(before: Date?, after: Date?) throws -> Set<Article>
|
||||
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock)
|
||||
}
|
||||
|
||||
@@ -36,6 +37,10 @@ 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)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
guard let account = account else {
|
||||
@@ -81,6 +86,10 @@ extension Folder: ArticleFetcher {
|
||||
return try account.fetchArticles(.folder(self, true))
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
|
||||
return try fetchArticles().unreadArticlesBetween(before: before, after: after)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
guard let account = account else {
|
||||
assertionFailure("Expected folder.account, but got nil.")
|
||||
|
||||
@@ -31,6 +31,10 @@ public struct SingleArticleFetcher: ArticleFetcher {
|
||||
public func fetchUnreadArticles() throws -> Set<Article> {
|
||||
return try account.fetchArticles(.articleIDs(Set([articleID])))
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
|
||||
return try account.fetchArticles(.articleIDs(Set([articleID])))
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
return account.fetchArticlesAsync(.articleIDs(Set([articleID])), completion)
|
||||
|
||||
@@ -79,6 +79,19 @@ 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})
|
||||
|
||||
@@ -106,6 +106,10 @@ public final class ArticlesDatabase {
|
||||
return try articlesTable.fetchUnreadArticles(webFeedIDs, limit)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesBetween(_ webFeedIDs: Set<String>, _ limit: Int?, _ before: Date?, _ after: Date?) throws -> Set<Article> {
|
||||
return try articlesTable.fetchUnreadArticlesBetween(webFeedIDs, limit, before, after)
|
||||
}
|
||||
|
||||
public func fetchTodayArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate(), limit)
|
||||
}
|
||||
|
||||
@@ -75,11 +75,15 @@ final class ArticlesTable: DatabaseTable {
|
||||
}
|
||||
|
||||
// MARK: - Fetching Unread Articles
|
||||
|
||||
|
||||
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, limit, $0) }
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesBetween(_ webFeedIDs: Set<String>, _ limit: Int?, _ before: Date?, _ after: Date?) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchUnreadArticlesBetween(webFeedIDs, limit, $0, before, after) }
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, limit, $0) }, completion)
|
||||
}
|
||||
@@ -845,6 +849,23 @@ private extension ArticlesTable {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesBetween(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase, _ before: Date?, _ after: Date?) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let 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 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)
|
||||
}
|
||||
|
||||
func fetchArticlesForFeedID(_ webFeedID: String, _ database: FMDatabase) -> Set<Article> {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject])
|
||||
}
|
||||
|
||||
@@ -167,6 +167,41 @@ 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>()
|
||||
|
||||
@@ -95,6 +95,10 @@ extension SmartFeed: ArticleFetcher {
|
||||
return try delegate.fetchUnreadArticles()
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
|
||||
return try delegate.fetchUnreadArticlesBetween(before: before, after: after)
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
delegate.fetchUnreadArticlesAsync(completion)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ extension SmartFeedDelegate {
|
||||
return try fetchArticles().unreadArticles()
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
|
||||
return try fetchArticles().unreadArticlesBetween(before: before, after: after)
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync{ articleSetResult in
|
||||
switch articleSetResult {
|
||||
|
||||
@@ -73,10 +73,14 @@ 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)
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
AccountManager.shared.fetchArticlesAsync(fetchType, completion)
|
||||
|
||||
@@ -905,16 +905,12 @@ private extension MasterFeedViewController {
|
||||
menuElements.append(UIMenu(title: "", options: .displayInline, children: pageActions))
|
||||
}
|
||||
|
||||
var markActions = [UIAction]()
|
||||
if let markAllAction = self.markAllAsReadAction(indexPath: indexPath) {
|
||||
markActions.append(markAllAction)
|
||||
}
|
||||
if !markActions.isEmpty {
|
||||
menuElements.append(UIMenu(title: "", options: .displayInline, children: markActions))
|
||||
|
||||
menuElements.append(UIMenu(title: "", options: .displayInline, children: [markAllAction]))
|
||||
|
||||
}
|
||||
|
||||
if let catchUpAction = self.catchUpAction(indexPath: indexPath) {
|
||||
if let catchUpAction = self.catchUpActionMenu(indexPath: indexPath) {
|
||||
menuElements.append(catchUpAction)
|
||||
}
|
||||
|
||||
@@ -1148,60 +1144,66 @@ private extension MasterFeedViewController {
|
||||
return action
|
||||
}
|
||||
|
||||
func catchUpAction(indexPath: IndexPath) -> UIMenu? {
|
||||
func catchUpActionMenu(indexPath: IndexPath) -> UIMenu? {
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
|
||||
let contentView = self.tableView.cellForRow(at: indexPath)?.contentView,
|
||||
feed.unreadCount > 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let localizedMenuText = NSLocalizedString("Mark Older Than as Read...", comment: "Command")
|
||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
|
||||
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: title, sourceType: contentView) { [weak self] in
|
||||
if let articles = try? feed.fetchUnreadArticles() {
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Day as Read", sourceType: contentView) { [weak self] in
|
||||
let cutoff = Calendar.current.date(byAdding: .day, value: -1, to: Date())
|
||||
if let articles = try? feed.fetchUnreadArticlesBetween(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: title, sourceType: contentView) { [weak self] in
|
||||
if let articles = try? feed.fetchUnreadArticles() {
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 2 Days as Read", sourceType: contentView) { [weak self] in
|
||||
let cutoff = Calendar.current.date(byAdding: .day, value: -2, to: Date())
|
||||
if let articles = try? feed.fetchUnreadArticlesBetween(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: title, sourceType: contentView) { [weak self] in
|
||||
if let articles = try? feed.fetchUnreadArticles() {
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 3 Days as Read", sourceType: contentView) { [weak self] in
|
||||
let cutoff = Calendar.current.date(byAdding: .day, value: -3, to: Date())
|
||||
if let articles = try? feed.fetchUnreadArticlesBetween(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: title, sourceType: contentView) { [weak self] in
|
||||
if let articles = try? feed.fetchUnreadArticles() {
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Week as Read", sourceType: contentView) { [weak self] in
|
||||
let cutoff = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())
|
||||
if let articles = try? feed.fetchUnreadArticlesBetween(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: title, sourceType: contentView) { [weak self] in
|
||||
if let articles = try? feed.fetchUnreadArticles() {
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 2 Weeks as Read", sourceType: contentView) { [weak self] in
|
||||
let cutoff = Calendar.current.date(byAdding: .weekOfYear, value: -2, to: Date())
|
||||
if let articles = try? feed.fetchUnreadArticlesBetween(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: title, sourceType: contentView) { [weak self] in
|
||||
if let articles = try? feed.fetchUnreadArticles() {
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Month as Read", sourceType: contentView) { [weak self] in
|
||||
let cutoff = Calendar.current.date(byAdding: .month, value: -1, to: Date())
|
||||
if let articles = try? feed.fetchUnreadArticlesBetween(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: title, sourceType: contentView) { [weak self] in
|
||||
if let articles = try? feed.fetchUnreadArticles() {
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Year as Read", sourceType: contentView) { [weak self] in
|
||||
let cutoff = Calendar.current.date(byAdding: .year, value: -1, to: Date())
|
||||
if let articles = try? feed.fetchUnreadArticlesBetween(before: cutoff, after: nil) {
|
||||
self?.coordinator.markAllAsRead(Array(articles))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,30 +46,23 @@ struct MarkAsReadAlertController {
|
||||
completion: @escaping (UIAlertAction) -> Void) -> UIAlertController where T: MarkAsReadAlertControllerSourceType {
|
||||
|
||||
|
||||
let title = NSLocalizedString("Mark as Read", comment: "Catch Up")
|
||||
let message = NSLocalizedString("Mark articles as read older than",
|
||||
comment: "Mark articles as read older than")
|
||||
let title = NSLocalizedString("Mark As Read", comment: "Mark As Read")
|
||||
let message = NSLocalizedString("You can turn this confirmation off in Settings.",
|
||||
comment: "You can turn this confirmation off in Settings.")
|
||||
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
|
||||
let settingsTitle = NSLocalizedString("Open Settings", comment: "Open Settings")
|
||||
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
|
||||
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) { _ in
|
||||
cancelCompletion?()
|
||||
}
|
||||
let oneDayAction = UIAlertAction(title: "1 Day", style: .default, handler: completion)
|
||||
let twoDaysAction = UIAlertAction(title: "2 Days", style: .default, handler: completion)
|
||||
let threeDaysAction = UIAlertAction(title: "3 Days", style: .default, handler: completion)
|
||||
let oneWeekAction = UIAlertAction(title: "1 Week", style: .default, handler: completion)
|
||||
let twoWeeksAction = UIAlertAction(title: "2 Weeks", style: .default, handler: completion)
|
||||
let oneMonthAction = UIAlertAction(title: "1 Month", style: .default, handler: completion)
|
||||
let oneYearAction = UIAlertAction(title: "1 Year", style: .default, handler: completion)
|
||||
let settingsAction = UIAlertAction(title: settingsTitle, style: .default) { _ in
|
||||
coordinator.showSettings(scrollToArticlesSection: true)
|
||||
}
|
||||
let markAction = UIAlertAction(title: confirmTitle, style: .default, handler: completion)
|
||||
|
||||
alertController.addAction(oneDayAction)
|
||||
alertController.addAction(twoDaysAction)
|
||||
alertController.addAction(threeDaysAction)
|
||||
alertController.addAction(oneWeekAction)
|
||||
alertController.addAction(twoWeeksAction)
|
||||
alertController.addAction(oneMonthAction)
|
||||
alertController.addAction(oneYearAction)
|
||||
alertController.addAction(markAction)
|
||||
alertController.addAction(settingsAction)
|
||||
alertController.addAction(cancelAction)
|
||||
|
||||
if let barButtonItem = sourceType as? UIBarButtonItem {
|
||||
|
||||
Reference in New Issue
Block a user