From 0e728114297b7e46ce18da38e858787c0013da36 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 18 Feb 2020 13:49:29 -0800 Subject: [PATCH] Implement mark as unread window for accounts that need it. Issue #1407 --- Frameworks/Account/AccountBehaviors.swift | 19 +++++++++++-------- .../Feedly/FeedlyAccountDelegate.swift | 2 +- Shared/Data/ArticleUtilities.swift | 19 +++++++++++++++++++ iOS/Article/ArticleViewController.swift | 2 ++ iOS/Article/WebViewController.swift | 13 ++++++++----- .../MasterTimelineViewController.swift | 13 +++++++++---- iOS/SceneCoordinator.swift | 1 + 7 files changed, 51 insertions(+), 18 deletions(-) diff --git a/Frameworks/Account/AccountBehaviors.swift b/Frameworks/Account/AccountBehaviors.swift index b3551f008..83b8aae9b 100644 --- a/Frameworks/Account/AccountBehaviors.swift +++ b/Frameworks/Account/AccountBehaviors.swift @@ -14,25 +14,28 @@ import Foundation user interface as much as possible. For example some sync services don't allow feeds to be in the root folder of the account. */ -public struct AccountBehaviors: OptionSet { +public typealias AccountBehaviors = [AccountBehavior] + +public enum AccountBehavior: Equatable { /** Account doesn't support copies of a feed that are in a folder to be made to the root folder. */ - public static let disallowFeedCopyInRootFolder = AccountBehaviors(rawValue: 1) + case disallowFeedCopyInRootFolder /** Account doesn't support feeds in the root folder. */ - public static let disallowFeedInRootFolder = AccountBehaviors(rawValue: 2) + case disallowFeedInRootFolder /** Account doesn't support OPML imports */ - public static let disallowOPMLImports = AccountBehaviors(rawValue: 4) + case disallowOPMLImports - public let rawValue: Int - public init(rawValue: Int) { - self.rawValue = rawValue - } + /** + Account doesn't allow mark as read after a period of days + */ + case disallowMarkAsUnreadAfterPeriod(Int) + } diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index 4e5600e5c..369e237f8 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -25,7 +25,7 @@ final class FeedlyAccountDelegate: AccountDelegate { // TODO: Kiel, if you decide not to support OPML import you will have to disallow it in the behaviors // See https://developer.feedly.com/v3/opml/ - var behaviors: AccountBehaviors = [.disallowFeedInRootFolder] + var behaviors: AccountBehaviors = [.disallowFeedInRootFolder, .disallowMarkAsUnreadAfterPeriod(31)] let isOPMLImportSupported = false diff --git a/Shared/Data/ArticleUtilities.swift b/Shared/Data/ArticleUtilities.swift index 997cb0ded..c5d2421b1 100644 --- a/Shared/Data/ArticleUtilities.swift +++ b/Shared/Data/ArticleUtilities.swift @@ -63,6 +63,25 @@ extension Article { var logicalDatePublished: Date { return datePublished ?? dateModified ?? status.dateArrived } + + var isAvailableToMarkUnread: Bool { + guard let markUnreadWindow = account?.behaviors.compactMap( { behavior -> Int? in + switch behavior { + case .disallowMarkAsUnreadAfterPeriod(let days): + return days + default: + return nil + } + }).first else { + return true + } + + if logicalDatePublished.byAdding(days: markUnreadWindow) > Date() { + return true + } else { + return false + } + } func iconImage() -> IconImage? { if let authors = authors, authors.count == 1, let author = authors.first { diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index e6fd7af8f..b189719dc 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -157,9 +157,11 @@ class ArticleViewController: UIViewController { if article.status.read { readBarButtonItem.image = AppAssets.circleOpenImage + readBarButtonItem.isEnabled = article.isAvailableToMarkUnread readBarButtonItem.accLabelText = NSLocalizedString("Mark Article Unread", comment: "Mark Article Unread") } else { readBarButtonItem.image = AppAssets.circleClosedImage + readBarButtonItem.isEnabled = true readBarButtonItem.accLabelText = NSLocalizedString("Selected - Mark Article Unread", comment: "Selected - Mark Article Unread") } diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift index b99f287c3..111a1e23f 100644 --- a/iOS/Article/WebViewController.swift +++ b/iOS/Article/WebViewController.swift @@ -264,7 +264,9 @@ extension WebViewController: UIContextMenuInteractionDelegate { if let action = self.nextArticleAction() { actions.append(action) } - actions.append(self.toggleReadAction()) + if let action = self.toggleReadAction() { + actions.append(action) + } actions.append(self.toggleStarredAction()) if let action = self.nextUnreadArticleAction() { actions.append(action) @@ -642,10 +644,11 @@ private extension WebViewController { } } - func toggleReadAction() -> UIAction { - let read = article?.status.read ?? false - let title = read ? NSLocalizedString("Mark as Unread", comment: "Mark as Unread") : NSLocalizedString("Mark as Read", comment: "Mark as Read") - let readImage = read ? AppAssets.circleClosedImage : AppAssets.circleOpenImage + func toggleReadAction() -> UIAction? { + guard let article = article, !article.status.read || article.isAvailableToMarkUnread else { return nil } + + let title = article.status.read ? NSLocalizedString("Mark as Unread", comment: "Mark as Unread") : NSLocalizedString("Mark as Read", comment: "Mark as Read") + let readImage = article.status.read ? AppAssets.circleClosedImage : AppAssets.circleOpenImage return UIAction(title: title, image: readImage) { [weak self] action in self?.coordinator.toggleReadForCurrentArticle() } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 4caa62a57..85417b3ab 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -217,7 +217,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { guard let article = dataSource.itemIdentifier(for: indexPath) else { return nil } - + guard !article.status.read || article.isAvailableToMarkUnread else { return nil } + // Set up the read action let readTitle = article.status.read ? NSLocalizedString("Unread", comment: "Unread") : @@ -314,7 +315,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner guard let self = self else { return nil } var actions = [UIAction]() - actions.append(self.toggleArticleReadStatusAction(article)) + if let action = self.toggleArticleReadStatusAction(article) { + actions.append(action) + } + actions.append(self.toggleArticleStarStatusAction(article)) if let action = self.markAboveAsReadAction(article) { @@ -672,8 +676,9 @@ private extension MasterTimelineViewController { return nil } - func toggleArticleReadStatusAction(_ article: Article) -> UIAction { - + func toggleArticleReadStatusAction(_ article: Article) -> UIAction? { + guard !article.status.read || article.isAvailableToMarkUnread else { return nil } + let title = article.status.read ? NSLocalizedString("Mark as Unread", comment: "Mark as Unread") : NSLocalizedString("Mark as Read", comment: "Mark as Read") diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 6129a8e69..83f617e56 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -1008,6 +1008,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func toggleRead(_ article: Article) { + guard !article.status.read || article.isAvailableToMarkUnread else { return } markArticlesWithUndo([article], statusKey: .read, flag: !article.status.read) }