diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index d8f99402f..accf4b67e 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -937,7 +937,7 @@ private extension MasterFeedViewController { guard let node = dataSource.itemIdentifier(for: indexPath), coordinator.unreadCountFor(node) > 0, let feed = node.representedObject as? WebFeed, - let articles = try? feed.fetchArticles() else { + let articles = try? feed.fetchArticles(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil } @@ -947,8 +947,9 @@ private extension MasterFeedViewController { completion(true) } + let action = UIAlertAction(title: title, style: .default) { [weak self] action in - MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, cancelCompletion: cancel) { [weak self] in + MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in self?.coordinator.markAllAsRead(Array(articles)) completion(true) } @@ -1026,7 +1027,7 @@ private extension MasterFeedViewController { } let articles = Array(fetchedArticles) - return markAllAsReadAction(articles: articles, nameForDisplay: articleFetcher.nameForDisplay) + return markAllAsReadAction(articles: articles, nameForDisplay: articleFetcher.nameForDisplay, indexPath: indexPath) } func markAllAsReadAction(account: Account) -> UIAction? { @@ -1038,16 +1039,16 @@ private extension MasterFeedViewController { return markAllAsReadAction(articles: articles, nameForDisplay: account.nameForDisplay) } - func markAllAsReadAction(articles: [Article], nameForDisplay: String) -> UIAction? { - guard articles.canMarkAllAsRead() else { + func markAllAsReadAction(articles: [Article], nameForDisplay: String, indexPath: IndexPath? = nil) -> UIAction? { + guard articles.canMarkAllAsRead(), let indexPath = indexPath, let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil } let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command") let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, nameForDisplay) as String - + let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in - MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title) { [weak self] in + MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in self?.coordinator.markAllAsRead(articles) } } diff --git a/iOS/MasterTimeline/MarkAsReadAlertController.swift b/iOS/MasterTimeline/MarkAsReadAlertController.swift index 670b2c637..241b734c2 100644 --- a/iOS/MasterTimeline/MarkAsReadAlertController.swift +++ b/iOS/MasterTimeline/MarkAsReadAlertController.swift @@ -9,13 +9,20 @@ import Foundation import UIKit +protocol MarkAsReadAlertControllerSourceType {} +extension CGRect: MarkAsReadAlertControllerSourceType {} +extension UIView: MarkAsReadAlertControllerSourceType {} +extension UIBarButtonItem: MarkAsReadAlertControllerSourceType {} + + struct MarkAsReadAlertController { - static func confirm(_ controller: UIViewController?, - coordinator: SceneCoordinator?, - confirmTitle: String, - cancelCompletion: (() -> Void)? = nil, - completion: @escaping () -> Void) { + static func confirm(_ controller: UIViewController?, + coordinator: SceneCoordinator?, + confirmTitle: String, + sourceType: T, + cancelCompletion: (() -> Void)? = nil, + completion: @escaping () -> Void) where T: MarkAsReadAlertControllerSourceType { guard let controller = controller, let coordinator = coordinator else { completion() @@ -23,7 +30,7 @@ struct MarkAsReadAlertController { } if AppDefaults.confirmMarkAllAsRead { - let alertController = MarkAsReadAlertController.alert(coordinator: coordinator, confirmTitle: confirmTitle, cancelCompletion: cancelCompletion) { _ in + let alertController = MarkAsReadAlertController.alert(coordinator: coordinator, confirmTitle: confirmTitle, cancelCompletion: cancelCompletion, sourceType: sourceType) { _ in completion() } controller.present(alertController, animated: true) @@ -32,10 +39,12 @@ struct MarkAsReadAlertController { } } - private static func alert(coordinator: SceneCoordinator, + private static func alert(coordinator: SceneCoordinator, confirmTitle: String, cancelCompletion: (() -> Void)?, - completion: @escaping (UIAlertAction) -> Void) -> UIAlertController { + sourceType: T, + completion: @escaping (UIAlertAction) -> Void) -> UIAlertController where T: MarkAsReadAlertControllerSourceType { + let title = NSLocalizedString("Mark As Read", comment: "Mark As Read") let message = NSLocalizedString("You can turn this confirmation off in settings.", @@ -43,7 +52,7 @@ struct MarkAsReadAlertController { let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel") let settingsTitle = NSLocalizedString("Open Settings", comment: "Open Settings") - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) { _ in cancelCompletion?() } @@ -55,6 +64,21 @@ struct MarkAsReadAlertController { alertController.addAction(markAction) alertController.addAction(settingsAction) alertController.addAction(cancelAction) + + if let barButtonItem = sourceType as? UIBarButtonItem { + alertController.popoverPresentationController?.barButtonItem = barButtonItem + return alertController + } + + if let rect = sourceType as? CGRect { + alertController.popoverPresentationController?.sourceRect = rect + return alertController + } + + if let view = sourceType as? UIView { + alertController.popoverPresentationController?.sourceView = view + return alertController + } return alertController } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index fdd148c81..54f1f8bb9 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -116,7 +116,12 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner @IBAction func markAllAsRead(_ sender: Any) { let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read") - MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title) { [weak self] in + + guard let barButtonItem = sender as? UIBarButtonItem else { + return + } + + MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title, sourceType: barButtonItem) { [weak self] in self?.coordinator.markAllAsReadInTimeline() } } @@ -260,11 +265,11 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner popoverController.sourceRect = CGRect(x: view.frame.size.width/2, y: view.frame.size.height/2, width: 1, height: 1) } - if let action = self.markAboveAsReadAlertAction(article, completion: completion) { + if let action = self.markAboveAsReadAlertAction(article, indexPath: indexPath, completion: completion) { alert.addAction(action) } - if let action = self.markBelowAsReadAlertAction(article, completion: completion) { + if let action = self.markBelowAsReadAlertAction(article, indexPath: indexPath, completion: completion) { alert.addAction(action) } @@ -272,7 +277,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner alert.addAction(action) } - if let action = self.markAllInFeedAsReadAlertAction(article, completion: completion) { + if let action = self.markAllInFeedAsReadAlertAction(article, indexPath: indexPath, completion: completion) { alert.addAction(action) } @@ -317,11 +322,11 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner actions.append(self.toggleArticleStarStatusAction(article)) - if let action = self.markAboveAsReadAction(article) { + if let action = self.markAboveAsReadAction(article, indexPath: indexPath) { actions.append(action) } - if let action = self.markBelowAsReadAction(article) { + if let action = self.markBelowAsReadAction(article, indexPath: indexPath) { actions.append(action) } @@ -329,7 +334,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner actions.append(action) } - if let action = self.markAllInFeedAsReadAction(article) { + if let action = self.markAllInFeedAsReadAction(article, indexPath: indexPath) { actions.append(action) } @@ -696,38 +701,38 @@ private extension MasterTimelineViewController { return action } - func markAboveAsReadAction(_ article: Article) -> UIAction? { - guard coordinator.canMarkAboveAsRead(for: article) else { + func markAboveAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? { + guard coordinator.canMarkAboveAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil } let title = NSLocalizedString("Mark Above as Read", comment: "Mark Above as Read") let image = AppAssets.markAboveAsReadImage let action = UIAction(title: title, image: image) { [weak self] action in - MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title) { [weak self] in + MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in self?.coordinator.markAboveAsRead(article) } } return action } - func markBelowAsReadAction(_ article: Article) -> UIAction? { - guard coordinator.canMarkBelowAsRead(for: article) else { + func markBelowAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? { + guard coordinator.canMarkBelowAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil } let title = NSLocalizedString("Mark Below as Read", comment: "Mark Below as Read") let image = AppAssets.markBelowAsReadImage let action = UIAction(title: title, image: image) { [weak self] action in - MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title) { [weak self] in + MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in self?.coordinator.markBelowAsRead(article) } } return action } - func markAboveAsReadAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? { - guard coordinator.canMarkAboveAsRead(for: article) else { + func markAboveAsReadAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? { + guard coordinator.canMarkAboveAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil } @@ -737,7 +742,7 @@ private extension MasterTimelineViewController { } let action = UIAlertAction(title: title, style: .default) { [weak self] action in - MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, cancelCompletion: cancel) { [weak self] in + MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in self?.coordinator.markAboveAsRead(article) completion(true) } @@ -745,8 +750,8 @@ private extension MasterTimelineViewController { return action } - func markBelowAsReadAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? { - guard coordinator.canMarkBelowAsRead(for: article) else { + func markBelowAsReadAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? { + guard coordinator.canMarkBelowAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil } @@ -756,7 +761,7 @@ private extension MasterTimelineViewController { } let action = UIAlertAction(title: title, style: .default) { [weak self] action in - MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, cancelCompletion: cancel) { [weak self] in + MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in self?.coordinator.markBelowAsRead(article) completion(true) } @@ -787,36 +792,37 @@ private extension MasterTimelineViewController { return action } - func markAllInFeedAsReadAction(_ article: Article) -> UIAction? { + func markAllInFeedAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? { guard let webFeed = article.webFeed else { return nil } guard let fetchedArticles = try? webFeed.fetchArticles() else { return nil } let articles = Array(fetchedArticles) - guard articles.canMarkAllAsRead() else { + guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil } + let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command") let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in - MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title) { [weak self] in + MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in self?.coordinator.markAllAsRead(articles) } } return action } - func markAllInFeedAsReadAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? { + func markAllInFeedAsReadAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? { guard let webFeed = article.webFeed else { return nil } guard let fetchedArticles = try? webFeed.fetchArticles() else { return nil } let articles = Array(fetchedArticles) - guard articles.canMarkAllAsRead() else { + guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil } @@ -827,7 +833,7 @@ private extension MasterTimelineViewController { } let action = UIAlertAction(title: title, style: .default) { [weak self] action in - MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, cancelCompletion: cancel) { [weak self] in + MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in self?.coordinator.markAllAsRead(articles) completion(true) }