mastodon-ios/Mastodon/Protocol/UserProvider/UserProviderFacade.swift

401 lines
18 KiB
Swift

//
// UserProviderFacade.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-4-1.
//
import Combine
import CoreData
import CoreDataStack
import MastodonSDK
import UIKit
enum UserProviderFacade {}
extension UserProviderFacade {
static func toggleUserFollowRelationship(
provider: UserProvider
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// prepare authentication
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
assertionFailure()
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
}
return _toggleUserFollowRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: provider.mastodonUser().eraseToAnyPublisher()
)
}
static func toggleUserFollowRelationship(
provider: UserProvider,
mastodonUser: MastodonUser
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// prepare authentication
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
assertionFailure()
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
}
return _toggleUserFollowRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: Just(mastodonUser).eraseToAnyPublisher()
)
}
private static func _toggleUserFollowRelationship(
context: AppContext,
activeMastodonAuthenticationBox: MastodonAuthenticationBox,
mastodonUser: AnyPublisher<MastodonUser?, Never>
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
mastodonUser
.compactMap { mastodonUser -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>? in
guard let mastodonUser = mastodonUser else {
return nil
}
return context.apiService.toggleFollow(
for: mastodonUser,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox
)
}
.switchToLatest()
.eraseToAnyPublisher()
}
}
extension UserProviderFacade {
static func toggleUserBlockRelationship(
provider: UserProvider,
mastodonUser: MastodonUser
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// prepare authentication
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
assertionFailure()
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
}
return _toggleUserBlockRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: Just(mastodonUser).eraseToAnyPublisher()
)
}
static func toggleUserBlockRelationship(
provider: UserProvider,
cell: UITableViewCell?
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// prepare authentication
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
assertionFailure()
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
}
if let cell = cell {
return _toggleUserBlockRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: provider.mastodonUser(for: cell).eraseToAnyPublisher()
)
} else {
return _toggleUserBlockRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: provider.mastodonUser().eraseToAnyPublisher()
)
}
}
private static func _toggleUserBlockRelationship(
context: AppContext,
activeMastodonAuthenticationBox: MastodonAuthenticationBox,
mastodonUser: AnyPublisher<MastodonUser?, Never>
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
mastodonUser
.compactMap { mastodonUser -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>? in
guard let mastodonUser = mastodonUser else {
return nil
}
return context.apiService.toggleBlock(
for: mastodonUser,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox
)
}
.switchToLatest()
.eraseToAnyPublisher()
}
}
extension UserProviderFacade {
static func toggleUserMuteRelationship(
provider: UserProvider,
mastodonUser: MastodonUser
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// prepare authentication
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
assertionFailure()
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
}
return _toggleUserMuteRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: Just(mastodonUser).eraseToAnyPublisher()
)
}
static func toggleUserMuteRelationship(
provider: UserProvider,
cell: UITableViewCell?
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// prepare authentication
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
assertionFailure()
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
}
if let cell = cell {
return _toggleUserMuteRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: provider.mastodonUser(for: cell).eraseToAnyPublisher()
)
} else {
return _toggleUserMuteRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: provider.mastodonUser().eraseToAnyPublisher()
)
}
}
private static func _toggleUserMuteRelationship(
context: AppContext,
activeMastodonAuthenticationBox: MastodonAuthenticationBox,
mastodonUser: AnyPublisher<MastodonUser?, Never>
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
mastodonUser
.compactMap { mastodonUser -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>? in
guard let mastodonUser = mastodonUser else {
return nil
}
return context.apiService.toggleMute(
for: mastodonUser,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox
)
}
.switchToLatest()
.eraseToAnyPublisher()
}
}
extension UserProviderFacade {
static func createProfileActionMenu(
for mastodonUser: MastodonUser,
isMyself: Bool,
isMuting: Bool,
isBlocking: Bool,
isInSameDomain: Bool,
isDomainBlocking: Bool,
provider: UserProvider,
cell: UITableViewCell?,
sourceView: UIView?,
barButtonItem: UIBarButtonItem?,
shareUser: MastodonUser?,
shareStatus: Status?
) -> UIMenu {
var children: [UIMenuElement] = []
let name = mastodonUser.displayNameWithFallback
if let shareUser = shareUser {
let shareAction = UIAction(title: L10n.Common.Controls.Actions.shareUser(name), image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let provider = provider else { return }
let activityViewController = createActivityViewControllerForMastodonUser(mastodonUser: shareUser, dependency: provider)
provider.coordinator.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: sourceView,
barButtonItem: barButtonItem
),
from: provider,
transition: .activityViewControllerPresent(animated: true, completion: nil)
)
}
children.append(shareAction)
}
if let shareStatus = shareStatus {
let shareAction = UIAction(title: L10n.Common.Controls.Actions.sharePost, image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let provider = provider else { return }
let activityViewController = createActivityViewControllerForMastodonUser(status: shareStatus, dependency: provider)
provider.coordinator.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: sourceView,
barButtonItem: barButtonItem
),
from: provider,
transition: .activityViewControllerPresent(animated: true, completion: nil)
)
}
children.append(shareAction)
}
if !isMyself {
// mute
let muteAction = UIAction(
title: isMuting ? L10n.Common.Controls.Friendship.unmuteUser(name) : L10n.Common.Controls.Friendship.mute,
image: isMuting ? UIImage(systemName: "speaker") : UIImage(systemName: "speaker.slash"),
discoverabilityTitle: isMuting ? nil : L10n.Common.Controls.Friendship.muteUser(name),
attributes: isMuting ? [] : .destructive,
state: .off
) { [weak provider] _ in
guard let provider = provider else { return }
UserProviderFacade.toggleUserMuteRelationship(
provider: provider,
cell: cell
)
.sink { _ in
// do nothing
} receiveValue: { _ in
// do nothing
}
.store(in: &provider.context.disposeBag)
}
if isMuting {
children.append(muteAction)
} else {
let muteMenu = UIMenu(title: L10n.Common.Controls.Friendship.muteUser(name), image: UIImage(systemName: "speaker.slash"), options: [], children: [muteAction])
children.append(muteMenu)
}
}
if !isMyself {
// block
let blockAction = UIAction(
title: isBlocking ? L10n.Common.Controls.Friendship.unblockUser(name) : L10n.Common.Controls.Friendship.block,
image: isBlocking ? UIImage(systemName: "hand.raised.slash") : UIImage(systemName: "hand.raised"),
discoverabilityTitle: isBlocking ? nil : L10n.Common.Controls.Friendship.blockUser(name),
attributes: isBlocking ? [] : .destructive,
state: .off
) { [weak provider] _ in
guard let provider = provider else { return }
UserProviderFacade.toggleUserBlockRelationship(
provider: provider,
cell: cell
)
.sink { _ in
// do nothing
} receiveValue: { _ in
// do nothing
}
.store(in: &provider.context.disposeBag)
}
if isBlocking {
children.append(blockAction)
} else {
let blockMenu = UIMenu(title: L10n.Common.Controls.Friendship.blockUser(name), image: UIImage(systemName: "hand.raised"), options: [], children: [blockAction])
children.append(blockMenu)
}
}
if !isMyself {
let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "flag"), 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)
}
if !isInSameDomain {
if isDomainBlocking {
let unblockDomainAction = UIAction(title: L10n.Common.Controls.Actions.unblockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let provider = provider else { return }
provider.context.blockDomainService.unblockDomain(userProvider: provider, cell: cell)
}
children.append(unblockDomainAction)
} else {
let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let provider = provider else { return }
let alertController = UIAlertController(title: L10n.Common.Alerts.BlockDomain.title(mastodonUser.domainFromAcct), message: nil, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
}
alertController.addAction(cancelAction)
let blockDomainAction = UIAlertAction(title: L10n.Common.Alerts.BlockDomain.blockEntireDomain, style: .destructive) { _ in
provider.context.blockDomainService.blockDomain(userProvider: provider, cell: cell)
}
alertController.addAction(blockDomainAction)
provider.present(alertController, animated: true, completion: nil)
}
children.append(blockDomainAction)
}
}
if let status = shareStatus, isMyself {
let deleteAction = UIAction(title: L10n.Common.Controls.Actions.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) {
[weak provider] _ in
guard let provider = provider else { return }
let alertController = UIAlertController(title: L10n.Common.Alerts.DeletePost.title, message: nil, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
}
alertController.addAction(cancelAction)
let deleteAction = UIAlertAction(title: L10n.Common.Alerts.DeletePost.delete, style: .destructive) { _ in
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
provider.context.apiService.deleteStatus(domain: activeMastodonAuthenticationBox.domain,
statusID: status.id,
authorizationBox: activeMastodonAuthenticationBox
)
.sink { _ in
// do nothing
} receiveValue: { _ in
// do nothing
}
.store(in: &provider.context.disposeBag)
}
alertController.addAction(deleteAction)
provider.present(alertController, animated: true, completion: nil)
}
children.append(deleteAction)
}
return UIMenu(title: "", options: [], children: children)
}
static func createActivityViewControllerForMastodonUser(mastodonUser: MastodonUser, dependency: NeedsDependency) -> UIActivityViewController {
let activityViewController = UIActivityViewController(
activityItems: mastodonUser.activityItems,
applicationActivities: [SafariActivity(sceneCoordinator: dependency.coordinator)]
)
return activityViewController
}
static func createActivityViewControllerForMastodonUser(status: Status, dependency: NeedsDependency) -> UIActivityViewController {
let activityViewController = UIActivityViewController(
activityItems: status.activityItems,
applicationActivities: [SafariActivity(sceneCoordinator: dependency.coordinator)]
)
return activityViewController
}
}