diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index d304816d0..ee55de483 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -394,8 +394,48 @@ extension DataSourceFacade { try await DataSourceFacade.responseToUserFollowAction(dependency: dependency, user: author) + case .blockDomain(let context): + let title: String + let message: String + let actionTitle: String + + #warning("Localization") + if context.isBlocking { + title = "Unblock \(context.domain)" + message = "Really unblock \(context.domain)" + actionTitle = L10n.Common.Controls.Friendship.unblockUser(context.domain) + } else { + title = "Block \(context.domain)" + message = "Really block \(context.domain)" + actionTitle = L10n.Common.Controls.Friendship.blockUser(context.domain) + } + let alertController = UIAlertController( + title: title, + message: message, + preferredStyle: .alert + ) + + let confirmAction = UIAlertAction(title: actionTitle, style: .destructive ) { [weak dependency] _ in + guard let dependency = dependency else { return } + Task { + let managedObjectContext = dependency.context.managedObjectContext + let _user: ManagedObjectRecord? = try? await managedObjectContext.perform { + guard let user = menuContext.author?.object(in: managedObjectContext) else { return nil } + return ManagedObjectRecord(objectID: user.objectID) + } + guard let user = _user else { return } + try await DataSourceFacade.responseToDomainBlockAction( + dependency: dependency, + user: user + ) + } + } + alertController.addAction(confirmAction) + let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel) + alertController.addAction(cancelAction) + dependency.present(alertController, animated: true) } - } // end func + } } extension DataSourceFacade { diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index a1c5e3925..3b7174c13 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -395,16 +395,16 @@ extension ProfileViewController { viewModel.relationshipViewModel.$optionSet ) .asyncMap { [weak self] user, relationshipSet -> UIMenu? in - guard let self = self else { return nil } - guard let user = user else { - return nil - } + guard let self, let user else { return nil } + let name = user.displayNameWithFallback + let domain = user.domainFromAcct let _ = ManagedObjectRecord(objectID: user.objectID) var menuActions: [MastodonMenu.Action] = [ .muteUser(.init(name: name, isMuting: self.viewModel.relationshipViewModel.isMuting)), .blockUser(.init(name: name, isBlocking: self.viewModel.relationshipViewModel.isBlocking)), + .blockDomain(.init(domain: domain, isBlocking: self.viewModel.relationshipViewModel.isDomainBlocking)), .reportUser(.init(name: name)), .shareUser(.init(name: name)), ] @@ -829,6 +829,27 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate { let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil) alertController.addAction(cancelAction) present(alertController, animated: true, completion: nil) + case .domainBlocking: + guard let user = viewModel.user else { return } + let name = user.displayNameWithFallback + + let alertController = UIAlertController( + title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.title, + message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.message(name), + preferredStyle: .alert + ) + let record = ManagedObjectRecord(objectID: user.objectID) + let unblockAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unblock, style: .default) { [weak self] _ in + guard let self = self else { return } + Task { + try await DataSourceFacade.responseToDomainBlockAction(dependency: self, user: record) + } + } + alertController.addAction(unblockAction) + let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil) + alertController.addAction(cancelAction) + present(alertController, animated: true, completion: nil) + case .blocking: guard let user = viewModel.user else { return } let name = user.displayNameWithFallback diff --git a/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift b/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift index 65231c51d..08085d561 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift @@ -59,7 +59,8 @@ extension MastodonMenu { case deleteStatus case editStatus case followUser(FollowUserActionContext) - + case blockDomain(BlockDomainActionContext) + func build(delegate: MastodonMenuDelegate) -> LabeledAction { switch self { case .hideReblogs(let context): @@ -194,14 +195,30 @@ extension MastodonMenu { image = UIImage(systemName: "person.fill.badge.plus") } let action = LabeledAction(title: title, image: image) { [weak delegate] in - guard let delegate = delegate else { return } + guard let delegate else { return } delegate.menuAction(self) } return action + case .blockDomain(let context): + let title: String + let image: UIImage? + //TODO: Add localization + if context.isBlocking { + title = "Unblock \(context.domain)" + image = UIImage(systemName: "hand.raised.slash.fill") + } else { + title = "Block \(context.domain)" + image = UIImage(systemName: "hand.raised.fill") + } + let action = LabeledAction(title: title, image: image) { [weak delegate] in + guard let delegate else { return } - } // end switch - } // end func build - } // end enum Action + delegate.menuAction(self) + } + return action + } + } + } } extension MastodonMenu { @@ -275,4 +292,14 @@ extension MastodonMenu { self.isFollowing = isFollowing } } + + public struct BlockDomainActionContext { + public let domain: String + public let isBlocking: Bool + + public init(domain: String, isBlocking: Bool) { + self.domain = domain + self.isBlocking = isBlocking + } + } } diff --git a/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift b/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift index 20f720d20..4d1a7eece 100644 --- a/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift @@ -28,6 +28,7 @@ public enum RelationshipAction: Int, CaseIterable { case edit case editing case updating + case domainBlocking public var option: RelationshipActionOptionSet { return RelationshipActionOptionSet(rawValue: 1 << rawValue) @@ -60,7 +61,8 @@ public struct RelationshipActionOptionSet: OptionSet { public static let updating = RelationshipAction.updating.option public static let showReblogs = RelationshipAction.showReblogs.option public static let editOptions: RelationshipActionOptionSet = [.edit, .editing, .updating] - + public static let domainBlocking = RelationshipAction.domainBlocking.option + public func highPriorityAction(except: RelationshipActionOptionSet) -> RelationshipAction? { let set = subtracting(except) for action in RelationshipAction.allCases.reversed() where set.contains(action.option) { @@ -92,6 +94,7 @@ public struct RelationshipActionOptionSet: OptionSet { case .editing: return L10n.Common.Controls.Actions.done case .updating: return " " case .showReblogs: return " " + case .domainBlocking: return " " } } } @@ -119,7 +122,8 @@ public final class RelationshipViewModel { @Published public var isBlocking = false @Published public var isBlockingBy = false @Published public var isSuspended = false - + @Published public var isDomainBlocking = false + public init() { Publishers.CombineLatest3( $user, @@ -171,9 +175,7 @@ extension RelationshipViewModel { extension RelationshipViewModel { private func update(user: MastodonUser?, me: MastodonUser?) { - guard let user = user, - let me = me - else { + guard let user, let me else { reset() return } @@ -188,6 +190,7 @@ extension RelationshipViewModel { self.isBlocking = optionSet.contains(.blocking) self.isSuspended = optionSet.contains(.suspended) self.showReblogs = optionSet.contains(.showReblogs) + self.isDomainBlocking = optionSet.contains(.domainBlocking) self.optionSet = optionSet }