diff --git a/Localization/app.json b/Localization/app.json index fcd960293..45ec698ad 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -52,7 +52,8 @@ "share": "Share", "share_user": "Share %s", "open_in_safari": "Open in Safari", - "skip": "Skip" + "skip": "Skip", + "report_user": "Report %s" }, "status": { "user_reblogged": "%s reblogged", diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 97e4cdf9c..ea3332f14 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -301,6 +301,9 @@ extension StatusSection { case is NotificationStatusTableViewCell: let notificationTableViewCell = cell as! NotificationStatusTableViewCell parent = notificationTableViewCell.delegate?.parent() + case is ReportedStatusTableViewCell: + let reportTableViewCell = cell as! ReportedStatusTableViewCell + parent = reportTableViewCell.dependency default: parent = nil assertionFailure("unknown cell") @@ -394,7 +397,12 @@ extension StatusSection { } // toolbar - StatusSection.configureActionToolBar(cell: cell, status: status, requestUserID: requestUserID) + StatusSection.configureActionToolBar( + cell: cell, + dependency: dependency, + status: status, + requestUserID: requestUserID + ) // separator line if let statusTableViewCell = cell as? StatusTableViewCell { @@ -418,7 +426,12 @@ extension StatusSection { } receiveValue: { change in guard case .update(let object) = change.changeType, let status = object as? Status else { return } - StatusSection.configureActionToolBar(cell: cell, status: status, requestUserID: requestUserID) + StatusSection.configureActionToolBar( + cell: cell, + dependency: dependency, + status: status, + requestUserID: requestUserID + ) os_log("%{public}s[%{public}ld], %{public}s: reblog count label for status %s did update: %ld", (#file as NSString).lastPathComponent, #line, #function, status.id, status.reblogsCount.intValue) os_log("%{public}s[%{public}ld], %{public}s: like count label for status %s did update: %ld", (#file as NSString).lastPathComponent, #line, #function, status.id, status.favouritesCount.intValue) @@ -573,6 +586,7 @@ extension StatusSection { static func configureActionToolBar( cell: StatusCell, + dependency: NeedsDependency, status: Status, requestUserID: String ) { @@ -600,6 +614,8 @@ extension StatusSection { }() cell.statusView.actionToolbarContainer.favoriteButton.setTitle(favoriteCountTitle, for: .normal) cell.statusView.actionToolbarContainer.isFavoriteButtonHighlight = isLike + + self.setupStatusMoreButtonMenu(cell: cell, dependency: dependency, status: status) } static func configurePoll( @@ -726,4 +742,37 @@ extension StatusSection { guard let number = number, number > 0 else { return "" } return String(number) } + + private static func setupStatusMoreButtonMenu( + cell: StatusCell, + dependency: NeedsDependency, + status: Status) { + + cell.statusView.actionToolbarContainer.moreButton.menu = nil + + guard let authenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value else { + return + } + let author = (status.reblog ?? status).author + guard authenticationBox.userID != author.id else { + return + } + var children: [UIMenuElement] = [] + let name = author.displayNameWithFallback + let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "exclamationmark.bubble"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { _ in + let viewModel = ReportViewModel( + context: dependency.context, + domain: authenticationBox.domain, + user: status.author, + status: status) + dependency.coordinator.present( + scene: .report(viewModel: viewModel), + from: nil, + transition: .modal(animated: true, completion: nil) + ) + } + children.append(reportAction) + cell.statusView.actionToolbarContainer.moreButton.menu = UIMenu(title: "", options: [], children: children) + cell.statusView.actionToolbarContainer.moreButton.showsMenuAsPrimaryAction = true + } } diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index d9baf8665..7fb024417 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -80,6 +80,10 @@ internal enum L10n { internal static let preview = L10n.tr("Localizable", "Common.Controls.Actions.Preview") /// Remove internal static let remove = L10n.tr("Localizable", "Common.Controls.Actions.Remove") + /// Report %@ + internal static func reportUser(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Actions.ReportUser", String(describing: p1)) + } /// Save internal static let save = L10n.tr("Localizable", "Common.Controls.Actions.Save") /// Save photo diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index b5f4dd32f..4f0a2bfee 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -218,6 +218,24 @@ extension UserProviderFacade { children.append(shareAction) } + let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "exclamationmark.bubble"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in + guard let provider = provider else { return } + guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { + return + } + let viewModel = ReportViewModel( + context: provider.context, + domain: authenticationBox.domain, + user: mastodonUser, + status: nil) + provider.coordinator.present( + scene: .report(viewModel: viewModel), + from: provider, + transition: .modal(animated: true, completion: nil) + ) + } + children.append(reportAction) + return UIMenu(title: "", options: [], children: children) } diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index e16d3e886..4ce29afd8 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -24,6 +24,7 @@ Please check your internet connection."; "Common.Controls.Actions.OpenInSafari" = "Open in Safari"; "Common.Controls.Actions.Preview" = "Preview"; "Common.Controls.Actions.Remove" = "Remove"; +"Common.Controls.Actions.ReportUser" = "Report %@"; "Common.Controls.Actions.Save" = "Save"; "Common.Controls.Actions.SavePhoto" = "Save photo"; "Common.Controls.Actions.SeeMore" = "See More"; diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index eaad67b7c..b1dd19447 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -41,10 +41,6 @@ extension HomeTimelineViewController { guard let self = self else { return } self.showSettings(action) }, - UIAction(title: "Report", image: UIImage(systemName: "exclamationmark.bubble"), attributes: []) { [weak self] action in - guard let self = self else { return } - self.showReportAction(action) - }, UIAction(title: "Sign Out", image: UIImage(systemName: "escape"), attributes: .destructive) { [weak self] action in guard let self = self else { return } self.signOutAction(action) @@ -339,42 +335,5 @@ extension HomeTimelineViewController { transition: .modal(animated: true, completion: nil) ) } - - @objc private func showReportAction(_ sender: UIAction) { - let alertController = UIAlertController(title: "Enter User ID", message: nil, preferredStyle: .alert) - alertController.addTextField() - alertController.addTextField() - guard let accountTextField = alertController.textFields?.first else { return } - guard let statusTextField = alertController.textFields?.last else { return } - accountTextField.placeholder = "User ID" - statusTextField.placeholder = "Status ID" - accountTextField.text = "212477" - statusTextField.text = "106103767536113615" - let showAction = UIAlertAction(title: "Show", style: .default) { [weak self] _ in - guard let self = self else { return } - - guard let userId = accountTextField.text else { return } - guard let statusId = statusTextField.text else { return } - guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return } - - // itodo: delete them - // 31803 - // 106093402888557459 - let viewModel = ReportViewModel( - context: self.context, - domain: authenticationBox.domain, - userId: userId, - statusId: statusId - ) - self.coordinator.present( - scene: .report(viewModel: viewModel), - from: self, transition: .modal(animated: true, completion: nil)) - } - alertController.addAction(showAction) - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) - } - } #endif diff --git a/Mastodon/Scene/Report/ReportViewController.swift b/Mastodon/Scene/Report/ReportViewController.swift index 9cf506a30..dea962dca 100644 --- a/Mastodon/Scene/Report/ReportViewController.swift +++ b/Mastodon/Scene/Report/ReportViewController.swift @@ -242,7 +242,7 @@ class ReportViewController: UIViewController, NeedsDependency { return nil } let request = MastodonUser.sortedFetchRequest - request.predicate = MastodonUser.predicate(domain: domain, id: viewModel.userId) + request.predicate = MastodonUser.predicate(domain: domain, id: viewModel.user.id) request.fetchLimit = 1 request.returnsObjectsAsFaults = false do { diff --git a/Mastodon/Scene/Report/ReportViewModel+Data.swift b/Mastodon/Scene/Report/ReportViewModel+Data.swift index dcb715fba..da22a4b95 100644 --- a/Mastodon/Scene/Report/ReportViewModel+Data.swift +++ b/Mastodon/Scene/Report/ReportViewModel+Data.swift @@ -22,6 +22,7 @@ extension ReportViewModel { context.apiService.userTimeline( domain: domain, accountID: accountId, + excludeReblogs: true, authorizationBox: authorizationBox ) .receive(on: DispatchQueue.main) @@ -30,7 +31,7 @@ extension ReportViewModel { case .failure(let error): os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fetch user timeline fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) guard let self = self else { return } - guard let reportStatusId = self.statusId else { return } + guard let reportStatusId = self.status?.id else { return } var statusIDs = self.statusFetchedResultsController.statusIDs.value guard statusIDs.contains(reportStatusId) else { return } @@ -44,7 +45,7 @@ extension ReportViewModel { guard let self = self else { return } var statusIDs = response.value.map { $0.id } - if let reportStatusId = self.statusId, !statusIDs.contains(reportStatusId) { + if let reportStatusId = self.status?.id, !statusIDs.contains(reportStatusId) { statusIDs.append(reportStatusId) } @@ -86,7 +87,7 @@ extension ReportViewModel { guard let status = managedObjectContext.object(with: objectID) as? Status else { continue } - if status.id == self.statusId { + if status.id == self.status?.id { attribute.isSelected = true self.append(statusID: status.id) self.continueEnableSubject.send(true) diff --git a/Mastodon/Scene/Report/ReportViewModel.swift b/Mastodon/Scene/Report/ReportViewModel.swift index 4864145f5..b787cf6c7 100644 --- a/Mastodon/Scene/Report/ReportViewModel.swift +++ b/Mastodon/Scene/Report/ReportViewModel.swift @@ -23,8 +23,8 @@ class ReportViewModel: NSObject { // confirm set only once weak var context: AppContext! { willSet { precondition(context == nil) } } - var userId: String - var statusId: String? + var user: MastodonUser + var status: Status? var statusIDs = [Mastodon.Entity.Status.ID]() var comment: String? @@ -56,12 +56,12 @@ class ReportViewModel: NSObject { init(context: AppContext, domain: String, - userId: String, - statusId: String? + user: MastodonUser, + status: Status? ) { self.context = context - self.userId = userId - self.statusId = statusId + self.user = user + self.status = status self.statusFetchedResultsController = StatusFetchedResultsController( managedObjectContext: context.managedObjectContext, domain: domain, @@ -69,7 +69,7 @@ class ReportViewModel: NSObject { ) self.reportQuery = FileReportQuery( - accountID: userId, + accountID: user.id, statusIDs: [], comment: nil, forward: nil @@ -97,7 +97,7 @@ class ReportViewModel: NSObject { requestRecentStatus( domain: domain, - accountId: self.userId, + accountId: self.user.id, authorizationBox: activeMastodonAuthenticationBox )