From 7f1edc94a0045e475dd9e2f0b7c64dae98066d30 Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 30 Oct 2023 18:23:07 -0400 Subject: [PATCH] Add swipe actions for posts --- .../Pages/PageListViewController+Menu.swift | 15 +++--- .../Pages/PageListViewController.swift | 16 +++++- .../Post/AbstractPostHelper+Actions.swift | 54 +++++++++++++++++++ .../Post/InteractivePostViewDelegate.swift | 6 ++- .../Post/PostListViewController.swift | 21 +++++++- WordPress/WordPress.xcodeproj/project.pbxproj | 6 +++ 6 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Post/AbstractPostHelper+Actions.swift diff --git a/WordPress/Classes/ViewRelated/Pages/PageListViewController+Menu.swift b/WordPress/Classes/ViewRelated/Pages/PageListViewController+Menu.swift index 27209f8cbc86..da901784f2af 100644 --- a/WordPress/Classes/ViewRelated/Pages/PageListViewController+Menu.swift +++ b/WordPress/Classes/ViewRelated/Pages/PageListViewController+Menu.swift @@ -28,9 +28,9 @@ extension PageListViewController: InteractivePostViewDelegate { publishPost(apost) } - func trash(_ apost: AbstractPost) { - guard let page = apost as? Page else { return } - trashPage(page) + func trash(_ post: AbstractPost, completion: @escaping () -> Void) { + guard let page = post as? Page else { return } + trashPage(page, completion: completion) } func draft(_ apost: AbstractPost) { @@ -90,7 +90,7 @@ extension PageListViewController: InteractivePostViewDelegate { present(editorViewController, animated: false) } - private func trashPage(_ page: Page) { + private func trashPage(_ page: Page, completion: @escaping () -> Void) { guard ReachabilityUtils.isInternetReachable() else { ReachabilityUtils.showNoInternetConnectionNotice(message: Strings.offlineMessage) return @@ -102,9 +102,12 @@ extension PageListViewController: InteractivePostViewDelegate { let messageText = isPageTrashed ? Strings.DeletePermanently.messageText : Strings.Trash.messageText let alertController = UIAlertController(title: titleText, message: messageText, preferredStyle: .alert) - alertController.addCancelActionWithTitle(Strings.cancelText) - alertController.addDestructiveActionWithTitle(actionText) { [weak self] action in + alertController.addCancelActionWithTitle(Strings.cancelText) { _ in + completion() + } + alertController.addDestructiveActionWithTitle(actionText) { [weak self] _ in self?.deletePost(page) + completion() } alertController.presentFromRootViewController() } diff --git a/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift b/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift index 6bc2854ff1da..3af36215c7b8 100644 --- a/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift +++ b/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift @@ -255,7 +255,7 @@ class PageListViewController: AbstractPostListViewController, UIViewControllerRe refreshResults() } - // MARK: - TableView Handler Delegate Methods + // MARK: - Core Data override func entityName() -> String { return String(describing: Page.self) @@ -333,6 +333,20 @@ class PageListViewController: AbstractPostListViewController, UIViewControllerRe } } + func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + indexPath.section == Section.pages.rawValue + } + + func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let actions = AbstractPostHelper.makeLeadingContextualActions(for: pages[indexPath.row], delegate: self) + return UISwipeActionsConfiguration(actions: actions) + } + + func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let actions = AbstractPostHelper.makeTrailingContextualActions(for: pages[indexPath.row], delegate: self) + return UISwipeActionsConfiguration(actions: actions) + } + // MARK: - UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { diff --git a/WordPress/Classes/ViewRelated/Post/AbstractPostHelper+Actions.swift b/WordPress/Classes/ViewRelated/Post/AbstractPostHelper+Actions.swift new file mode 100644 index 000000000000..4ade9227ec80 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/AbstractPostHelper+Actions.swift @@ -0,0 +1,54 @@ +import UIKit + +extension AbstractPostHelper { + // MARK: - Posts + + static func makeLeadingContextualActions(for post: AbstractPost, delegate: InteractivePostViewDelegate) -> [UIContextualAction] { + var actions: [UIContextualAction] = [] + + if post.status != .trash { + let viewAction = UIContextualAction(style: .normal, title: Strings.swipeActionView) { [weak delegate] _, _, completion in + delegate?.view(post) + completion(true) + } + viewAction.image = UIImage(systemName: "safari") + viewAction.backgroundColor = .systemBlue + actions.append(viewAction) + } + + return actions + } + + static func makeTrailingContextualActions(for post: AbstractPost, delegate: InteractivePostViewDelegate) -> [UIContextualAction] { + var actions: [UIContextualAction] = [] + + let trashAction = UIContextualAction( + style: .destructive, + title: post.status == .trash ? Strings.swipeActionDeletePermanently : Strings.swipeActionTrash + ) { [weak delegate] _, _, completion in + delegate?.trash(post) { + completion(true) + } + } + trashAction.image = UIImage(systemName: "trash") + actions.append(trashAction) + + if post.status == .publish && post.hasRemote() { + let shareAction = UIContextualAction(style: .normal, title: Strings.swipeActionShare) { [weak delegate] _, view, completion in + delegate?.share(post, fromView: view) + completion(true) + } + shareAction.image = UIImage(systemName: "square.and.arrow.up") + actions.append(shareAction) + } + + return actions + } +} + +private enum Strings { + static let swipeActionView = NSLocalizedString("postList.swipeActionView", value: "View", comment: "Swipe action title") + static let swipeActionShare = NSLocalizedString("postList.swipeActionShare", value: "Share", comment: "Swipe action title") + static let swipeActionTrash = NSLocalizedString("postList.swipeActionDelete", value: "Trash", comment: "Swipe action title") + static let swipeActionDeletePermanently = NSLocalizedString("postList.swipeActionDelete", value: "Delete Permanently", comment: "Swipe action title") +} diff --git a/WordPress/Classes/ViewRelated/Post/InteractivePostViewDelegate.swift b/WordPress/Classes/ViewRelated/Post/InteractivePostViewDelegate.swift index 4edee46492f4..b561f32d1283 100644 --- a/WordPress/Classes/ViewRelated/Post/InteractivePostViewDelegate.swift +++ b/WordPress/Classes/ViewRelated/Post/InteractivePostViewDelegate.swift @@ -6,7 +6,7 @@ protocol InteractivePostViewDelegate: AnyObject { func stats(for post: AbstractPost) func duplicate(_ post: AbstractPost) func publish(_ post: AbstractPost) - func trash(_ post: AbstractPost) + func trash(_ post: AbstractPost, completion: @escaping () -> Void) func draft(_ post: AbstractPost) func retry(_ post: AbstractPost) func cancelAutoUpload(_ post: AbstractPost) @@ -22,4 +22,8 @@ extension InteractivePostViewDelegate { func setParent(for post: AbstractPost, at indexPath: IndexPath) {} func setHomepage(for post: AbstractPost) {} func setPostsPage(for post: AbstractPost) {} + + func trash(_ post: AbstractPost) { + self.trash(post, completion: {}) + } } diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index ad70de58521b..a83c6f2eb25b 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -271,6 +271,20 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe } } + func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + true + } + + func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let actions = AbstractPostHelper.makeLeadingContextualActions(for: postAtIndexPath(indexPath), delegate: self) + return UISwipeActionsConfiguration(actions: actions) + } + + func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let actions = AbstractPostHelper.makeTrailingContextualActions(for: postAtIndexPath(indexPath), delegate: self) + return UISwipeActionsConfiguration(actions: actions) + } + // MARK: - Post Actions override func createPost() { @@ -359,7 +373,7 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe copyPostLink(post) } - func trash(_ post: AbstractPost) { + func trash(_ post: AbstractPost, completion: @escaping () -> Void) { guard ReachabilityUtils.isInternetReachable() else { let offlineMessage = NSLocalizedString("Unable to trash posts while offline. Please try again later.", comment: "Message that appears when a user tries to trash a post while their device is offline.") ReachabilityUtils.showNoInternetConnectionNotice(message: offlineMessage) @@ -385,9 +399,12 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe let alertController = UIAlertController(title: titleText, message: messageText, preferredStyle: .alert) - alertController.addCancelActionWithTitle(cancelText) + alertController.addCancelActionWithTitle(cancelText) { _ in + completion() + } alertController.addDestructiveActionWithTitle(deleteText) { [weak self] action in self?.deletePost(post) + completion() } alertController.presentFromRootViewController() } diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 433d84ef71a4..ef2be019b484 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -513,6 +513,8 @@ 0CF0C4232AE98C13006FFAB4 /* AbstractPostHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF0C4222AE98C13006FFAB4 /* AbstractPostHelper.swift */; }; 0CF0C4242AE98C13006FFAB4 /* AbstractPostHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF0C4222AE98C13006FFAB4 /* AbstractPostHelper.swift */; }; 0CF7D6C32ABB753A006D1E89 /* MediaImageServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF7D6C22ABB753A006D1E89 /* MediaImageServiceTests.swift */; }; + 0CFE9AC62AF44A9F00B8F659 /* AbstractPostHelper+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFE9AC52AF44A9F00B8F659 /* AbstractPostHelper+Actions.swift */; }; + 0CFE9AC72AF44A9F00B8F659 /* AbstractPostHelper+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFE9AC52AF44A9F00B8F659 /* AbstractPostHelper+Actions.swift */; }; 1702BBDC1CEDEA6B00766A33 /* BadgeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1702BBDB1CEDEA6B00766A33 /* BadgeLabel.swift */; }; 1702BBE01CF3034E00766A33 /* DomainsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1702BBDF1CF3034E00766A33 /* DomainsService.swift */; }; 17039225282E6D2800F602E9 /* ViewsVisitorsLineChartCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC772B0728201F5300664C02 /* ViewsVisitorsLineChartCell.swift */; }; @@ -6189,6 +6191,7 @@ 0CF0C4222AE98C13006FFAB4 /* AbstractPostHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractPostHelper.swift; sourceTree = ""; }; 0CF7D6C22ABB753A006D1E89 /* MediaImageServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaImageServiceTests.swift; sourceTree = ""; }; 0CFD6C792A73E703003DD0A0 /* WordPress 152.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 152.xcdatamodel"; sourceTree = ""; }; + 0CFE9AC52AF44A9F00B8F659 /* AbstractPostHelper+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AbstractPostHelper+Actions.swift"; sourceTree = ""; }; 131D0EE49695795ECEDAA446 /* Pods-WordPressTest.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressTest.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressTest/Pods-WordPressTest.release-alpha.xcconfig"; sourceTree = ""; }; 150B6590614A28DF9AD25491 /* Pods-Apps-Jetpack.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Apps-Jetpack.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-Apps-Jetpack/Pods-Apps-Jetpack.release-alpha.xcconfig"; sourceTree = ""; }; 152F25D5C232985E30F56CAC /* Pods-Apps-Jetpack.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Apps-Jetpack.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Apps-Jetpack/Pods-Apps-Jetpack.debug.xcconfig"; sourceTree = ""; }; @@ -12576,6 +12579,7 @@ FACF66CC2ADD645C008C3E13 /* PostListHeaderView.swift */, FACF66CF2ADD6CD8008C3E13 /* PostListItemViewModel.swift */, 0CF0C4222AE98C13006FFAB4 /* AbstractPostHelper.swift */, + 0CFE9AC52AF44A9F00B8F659 /* AbstractPostHelper+Actions.swift */, FAD3DE802AE2965A00A3B031 /* AbstractPostMenuHelper.swift */, FAEC116D2AEBEEA600F9DA54 /* AbstractPostMenuViewModel.swift */, FA141F262AEC1D9E00C9A653 /* PageMenuViewModel.swift */, @@ -21875,6 +21879,7 @@ 85DA8C4418F3F29A0074C8A4 /* WPAnalyticsTrackerWPCom.m in Sources */, B50C0C5E1EF42A4A00372C65 /* AztecAttachmentViewController.swift in Sources */, 43FB3F411EBD215C00FC8A62 /* LoginEpilogueBlogCell.swift in Sources */, + 0CFE9AC62AF44A9F00B8F659 /* AbstractPostHelper+Actions.swift in Sources */, 57D66B9A234BB206005A2D74 /* PostServiceRemoteFactory.swift in Sources */, 3FC8D19B244F43B500495820 /* ReaderTabItemsStore.swift in Sources */, 98EB126A20D2DC2500D2D5B5 /* NoResultsViewController+Model.swift in Sources */, @@ -25348,6 +25353,7 @@ FABB25312602FC2C00C8785C /* UIImageView+SiteIcon.swift in Sources */, FABB25322602FC2C00C8785C /* ReaderSiteSearchService.swift in Sources */, FABB25332602FC2C00C8785C /* QuickStartNavigationSettings.swift in Sources */, + 0CFE9AC72AF44A9F00B8F659 /* AbstractPostHelper+Actions.swift in Sources */, FABB25342602FC2C00C8785C /* WPImageViewController.m in Sources */, FABB25352602FC2C00C8785C /* ReaderListTopic.swift in Sources */, FABB25362602FC2C00C8785C /* PublicizeService.swift in Sources */,