diff --git a/Pachyderm/Sources/Pachyderm/Model/Account.swift b/Pachyderm/Sources/Pachyderm/Model/Account.swift index fa7ead8e..f22f36cc 100644 --- a/Pachyderm/Sources/Pachyderm/Model/Account.swift +++ b/Pachyderm/Sources/Pachyderm/Model/Account.swift @@ -112,12 +112,12 @@ public final class Account: AccountProtocol, Decodable { return Request(method: .post, path: "/api/v1/accounts/\(accountID)/unfollow") } - public static func block(_ account: Account) -> Request { - return Request(method: .post, path: "/api/v1/accounts/\(account.id)/block") + public static func block(_ accountID: String) -> Request { + return Request(method: .post, path: "/api/v1/accounts/\(accountID)/block") } - public static func unblock(_ account: Account) -> Request { - return Request(method: .post, path: "/api/v1/accounts/\(account.id)/unblock") + public static func unblock(_ accountID: String) -> Request { + return Request(method: .post, path: "/api/v1/accounts/\(accountID)/unblock") } public static func mute(_ account: Account, notifications: Bool? = nil) -> Request { diff --git a/Tusker/Screens/Utilities/Previewing.swift b/Tusker/Screens/Utilities/Previewing.swift index 33dc324d..cb94b717 100644 --- a/Tusker/Screens/Utilities/Previewing.swift +++ b/Tusker/Screens/Utilities/Previewing.swift @@ -42,46 +42,6 @@ extension MenuActionProvider { guard let mastodonController = mastodonController, let account = mastodonController.persistentContainer.account(for: accountID) else { return [] } - guard let loggedInAccountID = mastodonController.accountInfo?.id else { - // user is logged out - return [ - openInSafariAction(url: account.url), - createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in - guard let self = self else { return } - self.navigationDelegate?.showMoreOptions(forAccount: accountID, sourceView: sourceView) - }) - ] - } - - let actionsSection: [UIMenuElement] = [ - createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { [weak self] (_) in - guard let self = self else { return } - let draft = self.mastodonController!.createDraft(mentioningAcct: account.acct) - draft.visibility = .direct - self.navigationDelegate?.compose(editing: draft) - }), - UIDeferredMenuElement.uncached({ @MainActor [unowned self] elementHandler in - let relationship = Task { - await fetchRelationship(accountID: accountID, mastodonController: mastodonController) - } - // workaround for #198, may result in showing outdated relationship, so only do so where necessary - if ProcessInfo.processInfo.isiOSAppOnMac, - let mo = mastodonController.persistentContainer.relationship(forAccount: accountID), - let action = self.followAction(for: mo, mastodonController: mastodonController) { - elementHandler([action]) - } else { - Task { @MainActor in - if let relationship = await relationship.value, - let action = self.followAction(for: relationship, mastodonController: mastodonController) { - elementHandler([action]) - } else { - elementHandler([]) - } - } - } - }) - ] - var shareSection = [ openInSafariAction(url: account.url), createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in @@ -90,11 +50,32 @@ extension MenuActionProvider { }) ] + guard let loggedInAccountID = mastodonController.accountInfo?.id else { + // user is logged out + return shareSection + } + + var actionsSection: [UIMenuElement] = [ + createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { [weak self] (_) in + guard let self = self else { return } + let draft = self.mastodonController!.createDraft(mentioningAcct: account.acct) + draft.visibility = .direct + self.navigationDelegate?.compose(editing: draft) + }) + ] + var suppressSection: [UIMenuElement] = [] + + if accountID != loggedInAccountID { + actionsSection.append(relationshipAction(accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.followAction(for: $0, mastodonController: $1) })) + suppressSection.append(relationshipAction(accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.blockAction(for: $0, mastodonController: $1) })) + } + addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showProfileActivity(id: accountID, accountID: loggedInAccountID)) return [ - UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection), - UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: actionsSection), + UIMenu(options: .displayInline, children: shareSection), + UIMenu(options: .displayInline, children: actionsSection), + UIMenu(options: .displayInline, children: suppressSection), ] } @@ -367,12 +348,29 @@ extension MenuActionProvider { }) } + private func relationshipAction(accountID: String, mastodonController: MastodonController, builder: @escaping @MainActor (RelationshipMO, MastodonController) -> UIMenuElement) -> UIDeferredMenuElement { + return UIDeferredMenuElement.uncached({ @MainActor elementHandler in + let relationship = Task { + await fetchRelationship(accountID: accountID, mastodonController: mastodonController) + } + // workaround for #198, may result in showing outdated relationship, so only do so where necessary + if ProcessInfo.processInfo.isiOSAppOnMac, + let mo = mastodonController.persistentContainer.relationship(forAccount: accountID) { + elementHandler([builder(mo, mastodonController)]) + } else { + Task { @MainActor in + if let relationship = await relationship.value { + elementHandler([builder(relationship, mastodonController)]) + } else { + elementHandler([]) + } + } + } + }) + } + @MainActor - private func followAction(for relationship: RelationshipMO, mastodonController: MastodonController) -> UIMenuElement? { - guard let ownAccount = mastodonController.account, - relationship.accountID != ownAccount.id else { - return nil - } + private func followAction(for relationship: RelationshipMO, mastodonController: MastodonController) -> UIMenuElement { let accountID = relationship.accountID let following = relationship.following return createAction(identifier: "follow", title: following ? "Unfollow" : "Follow", systemImageName: following ? "person.badge.minus" : "person.badge.plus") { _ in @@ -393,6 +391,36 @@ extension MenuActionProvider { } } + @MainActor + private func blockAction(for relationship: RelationshipMO, mastodonController: MastodonController) -> UIMenuElement { + let accountID = relationship.accountID + let blocking = relationship.blocking + let handler = { (_: UIAction) in + let request = (blocking ? Account.unblock : Account.block)(accountID) + mastodonController.run(request) { response in + switch response { + case .failure(let error): + if let toastable = self.toastableViewController { + let config = ToastConfiguration(from: error, with: "Error \(blocking ? "Unb" : "B")locking", in: toastable, retryAction: nil) + DispatchQueue.main.async { + toastable.showToast(configuration: config, animated: true) + } + } + case .success(let relationship, _): + mastodonController.persistentContainer.addOrUpdate(relationship: relationship) + } + } + } + if blocking { + return createAction(identifier: "block", title: "Unblock", systemImageName: "circle.slash", handler: handler) + } else { + return UIMenu(title: "Block", image: UIImage(systemName: "circle.slash"), children: [ + UIAction(title: "Cancel", handler: { _ in }), + UIAction(title: "Block \(relationship.account!.displayOrUserName)", image: UIImage(systemName: "circle.slash"), attributes: .destructive, handler: handler), + ]) + } + } + } private func fetchRelationship(accountID: String, mastodonController: MastodonController) async -> RelationshipMO? {