chore: add BlockDomainService

This commit is contained in:
sunxiaojian 2021-04-30 14:55:02 +08:00
parent 0403cc0109
commit 8a5c62990e
15 changed files with 170 additions and 175 deletions

View File

@ -51,7 +51,7 @@ extension DomainBlock {
static func predicate(userID: String) -> NSPredicate {
NSPredicate(format: "%K == %@", #keyPath(DomainBlock.userID), userID)
}
static func predicate(blockedDomain: String) -> NSPredicate {
NSPredicate(format: "%K == %@", #keyPath(DomainBlock.blockedDomain), blockedDomain)
}
@ -62,12 +62,12 @@ extension DomainBlock {
DomainBlock.predicate(userID: userID)
])
}
public static func predicate(domain: String, userID: String, blockedDomain: String) -> NSPredicate {
NSCompoundPredicate(andPredicateWithSubpredicates: [
DomainBlock.predicate(domain: domain),
DomainBlock.predicate(userID: userID),
DomainBlock.predicate(blockedDomain:blockedDomain)
DomainBlock.predicate(blockedDomain: blockedDomain)
])
}
}

View File

@ -333,7 +333,7 @@ extension MastodonUser: Managed {
extension MastodonUser {
public static func predicate(domain: String) -> NSPredicate {
static func predicate(domain: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.domain), domain)
}
@ -369,4 +369,5 @@ extension MastodonUser {
MastodonUser.predicate(username: username)
])
}
}

View File

@ -310,7 +310,7 @@ extension Status: Managed {
extension Status {
public static func predicate(domain: String) -> NSPredicate {
static func predicate(domain: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(Status.domain), domain)
}

View File

@ -628,18 +628,18 @@ extension StatusSection {
cell.statusView.actionToolbarContainer.favoriteButton.setTitle(favoriteCountTitle, for: .normal)
cell.statusView.actionToolbarContainer.isFavoriteButtonHighlight = isLike
ManagedObjectObserver.observe(object: status.authorForUserProvider)
.receive(on: DispatchQueue.main)
.sink { _ in
// do nothing
} receiveValue: { [weak dependency, weak cell] change in
guard let cell = cell else { return }
guard let dependency = dependency else { return }
if case .update( _) = change.changeType {
StatusSection.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status)
}
}
.store(in: &cell.disposeBag)
Publishers.CombineLatest(
dependency.context.blockDomainService.blockedDomains,
ManagedObjectObserver.observe(object: status.authorForUserProvider)
.assertNoFailure()
)
.receive(on: DispatchQueue.main)
.sink { [weak dependency, weak cell] domains,change in
guard let cell = cell else { return }
guard let dependency = dependency else { return }
StatusSection.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status)
}
.store(in: &cell.disposeBag)
self.setupStatusMoreButtonMenu(cell: cell, indexPath: indexPath, dependency: dependency, status: status)
}
@ -784,40 +784,22 @@ extension StatusSection {
let isInSameDomain = authenticationBox.domain == author.domainFromAcct
let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
let isDomainBlocking = dependency.context.blockDomainService.blockedDomains.value.contains(author.domainFromAcct)
cell.statusView.actionToolbarContainer.moreButton.showsMenuAsPrimaryAction = true
let managedObjectContext = userProvider.context.backgroundManagedObjectContext
managedObjectContext.perform {
let blockedDomain: DomainBlock? = {
let request = DomainBlock.sortedFetchRequest
request.predicate = DomainBlock.predicate(domain: authenticationBox.domain, userID: authenticationBox.userID, blockedDomain: author.domainFromAcct)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
return try managedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}()
let isDomainBlocking = blockedDomain != nil
DispatchQueue.main.async {
cell.statusView.actionToolbarContainer.moreButton.menu = UserProviderFacade.createProfileActionMenu(
for: author,
isMuting: isMuting,
isBlocking: isBlocking,
canReport: canReport,
isInSameDomain: isInSameDomain,
isDomainBlocking: isDomainBlocking,
provider: userProvider,
cell: cell,
indexPath: indexPath,
sourceView: cell.statusView.actionToolbarContainer.moreButton,
barButtonItem: nil,
shareUser: nil,
shareStatus: status
)
}
}
cell.statusView.actionToolbarContainer.moreButton.menu = UserProviderFacade.createProfileActionMenu(
for: author,
isMuting: isMuting,
isBlocking: isBlocking,
canReport: canReport,
isInSameDomain: isInSameDomain,
isDomainBlocking: isDomainBlocking,
provider: userProvider,
cell: cell,
indexPath: indexPath,
sourceView: cell.statusView.actionToolbarContainer.moreButton,
barButtonItem: nil,
shareUser: nil,
shareStatus: status
)
}
}

View File

@ -5,8 +5,8 @@
// Created by MainasuK Cirno on 2021/2/4.
//
import Foundation
import CoreDataStack
import Foundation
import MastodonSDK
extension Status.Property {
@ -34,7 +34,6 @@ extension Status.Property {
}
extension Status {
enum SensitiveType {
case none
case all
@ -61,7 +60,6 @@ extension Status {
// not sensitive
return .none
}
}
extension Status {
@ -72,10 +70,10 @@ extension Status {
}
extension Status {
var statusURL: URL {
if let urlString = self.url,
let url = URL(string: urlString) {
let url = URL(string: urlString)
{
return url
} else {
return URL(string: "https://\(self.domain)/web/statuses/\(self.id)")!
@ -84,7 +82,7 @@ extension Status {
var activityItems: [Any] {
var items: [Any] = []
items.append(statusURL)
items.append(self.statusURL)
return items
}
}

View File

@ -5,21 +5,21 @@
// Created by MainasuK Cirno on 2021-4-1.
//
import UIKit
import Combine
import CoreData
import CoreDataStack
import UIKit
protocol UserProvider: NeedsDependency & DisposeBagCollectable & UIViewController {
// async
func mastodonUser() -> Future<MastodonUser?, Never>
func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future<MastodonUser?, Never>
}
extension UserProvider where Self: StatusProvider {
func mastodonUser(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future<MastodonUser?, Never> {
return Future { [weak self] promise in
Future { [weak self] promise in
guard let self = self else { return }
self.status(for: cell, indexPath: indexPath)
.sink { status in
@ -28,9 +28,9 @@ extension UserProvider where Self: StatusProvider {
.store(in: &self.disposeBag)
}
}
func mastodonUser() -> Future<MastodonUser?, Never> {
return Future { promise in
Future { promise in
promise(.success(nil))
}
}

View File

@ -5,16 +5,15 @@
// Created by MainasuK Cirno on 2021-4-1.
//
import UIKit
import Combine
import CoreData
import CoreDataStack
import MastodonSDK
import UIKit
enum UserProviderFacade { }
enum UserProviderFacade {}
extension UserProviderFacade {
static func toggleUserFollowRelationship(
provider: UserProvider
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
@ -50,11 +49,9 @@ extension UserProviderFacade {
.switchToLatest()
.eraseToAnyPublisher()
}
}
extension UserProviderFacade {
static func toggleUserBlockRelationship(
provider: UserProvider,
cell: UITableViewCell?,
@ -99,11 +96,9 @@ extension UserProviderFacade {
.switchToLatest()
.eraseToAnyPublisher()
}
}
extension UserProviderFacade {
static func toggleUserMuteRelationship(
provider: UserProvider,
cell: UITableViewCell?,
@ -148,11 +143,9 @@ extension UserProviderFacade {
.switchToLatest()
.eraseToAnyPublisher()
}
}
extension UserProviderFacade {
static func createProfileActionMenu(
for mastodonUser: MastodonUser,
isMuting: Bool,
@ -238,7 +231,8 @@ extension UserProviderFacade {
context: provider.context,
domain: authenticationBox.domain,
user: mastodonUser,
status: nil)
status: nil
)
provider.coordinator.present(
scene: .report(viewModel: viewModel),
from: provider,
@ -252,11 +246,7 @@ extension UserProviderFacade {
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 }
BlockDomainService(userProvider: provider,
cell: cell,
indexPath: indexPath
)
.unblockDomain()
provider.context.blockDomainService.unblockDomain(userProvider: provider, cell: cell, indexPath: indexPath)
}
children.append(unblockDomainAction)
} else {
@ -264,15 +254,10 @@ extension UserProviderFacade {
guard let provider = provider else { return }
let alertController = UIAlertController(title: "", message: L10n.Common.Alerts.BlockDomain.message(mastodonUser.domainFromAcct), 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
BlockDomainService(userProvider: provider,
cell: cell,
indexPath: indexPath
)
.blockDomain()
provider.context.blockDomainService.blockDomain(userProvider: provider, cell: cell, indexPath: indexPath)
}
alertController.addAction(blockDomainAction)
provider.present(alertController, animated: true, completion: nil)
@ -333,5 +318,4 @@ extension UserProviderFacade {
)
return activityViewController
}
}

View File

@ -366,36 +366,40 @@ extension ProfileViewController {
.receive(on: DispatchQueue.main)
.assign(to: \.text, on: profileHeaderViewController.profileHeaderView.usernameLabel)
.store(in: &disposeBag)
viewModel.relationshipActionOptionSet
.receive(on: DispatchQueue.main)
.sink { [weak self] relationshipActionOptionSet in
guard let self = self else { return }
guard let mastodonUser = self.viewModel.mastodonUser.value else {
self.moreMenuBarButtonItem.menu = nil
return
}
guard let currentDomain = self.viewModel.domain.value else { return }
let isMuting = relationshipActionOptionSet.contains(.muting)
let isBlocking = relationshipActionOptionSet.contains(.blocking)
let isDomainBlocking = relationshipActionOptionSet.contains(.domainBlocking)
let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value
let isInSameDomain = mastodonUser.domainFromAcct == currentDomain
self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu(
for: mastodonUser,
isMuting: isMuting,
isBlocking: isBlocking,
canReport: true,
isInSameDomain: isInSameDomain,
isDomainBlocking: isDomainBlocking,
provider: self,
cell: nil,
indexPath: nil,
sourceView: nil,
barButtonItem: self.moreMenuBarButtonItem,
shareUser: needsShareAction ? mastodonUser : nil,
shareStatus: nil)
Publishers.CombineLatest(
viewModel.relationshipActionOptionSet,
viewModel.context.blockDomainService.blockedDomains
)
.receive(on: DispatchQueue.main)
.sink { [weak self] relationshipActionOptionSet,domains in
guard let self = self else { return }
guard let mastodonUser = self.viewModel.mastodonUser.value else {
self.moreMenuBarButtonItem.menu = nil
return
}
.store(in: &disposeBag)
guard let currentDomain = self.viewModel.domain.value else { return }
let isMuting = relationshipActionOptionSet.contains(.muting)
let isBlocking = relationshipActionOptionSet.contains(.blocking)
let isDomainBlocking = domains.contains(mastodonUser.domainFromAcct)
let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value
let isInSameDomain = mastodonUser.domainFromAcct == currentDomain
self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu(
for: mastodonUser,
isMuting: isMuting,
isBlocking: isBlocking,
canReport: true,
isInSameDomain: isInSameDomain,
isDomainBlocking: isDomainBlocking,
provider: self,
cell: nil,
indexPath: nil,
sourceView: nil,
barButtonItem: self.moreMenuBarButtonItem,
shareUser: needsShareAction ? mastodonUser : nil,
shareStatus: nil)
}
.store(in: &disposeBag)
viewModel.isRelationshipActionButtonHidden
.receive(on: DispatchQueue.main)
.sink { [weak self] isHidden in

View File

@ -32,6 +32,19 @@ extension APIService {
)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> in
self.backgroundManagedObjectContext.performChanges {
let blockedDomains: [DomainBlock] = {
let request = DomainBlock.sortedFetchRequest
request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID)
request.returnsObjectsAsFaults = false
do {
return try self.backgroundManagedObjectContext.fetch(request)
} catch {
assertionFailure(error.localizedDescription)
return []
}
}()
blockedDomains.forEach { self.backgroundManagedObjectContext.delete($0) }
response.value.forEach { domain in
// use constrain to avoid repeated save
_ = DomainBlock.insert(
@ -74,12 +87,6 @@ extension APIService {
requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: authorizationBox.domain, id: authorizationBox.userID)
requestMastodonUserRequest.fetchLimit = 1
guard let requestMastodonUser = self.backgroundManagedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }
_ = DomainBlock.insert(
into: self.backgroundManagedObjectContext,
blockedDomain: user.domainFromAcct,
domain: authorizationBox.domain,
userID: authorizationBox.userID
)
user.update(isDomainBlocking: true, by: requestMastodonUser)
}
.setFailureType(to: Error.self)
@ -110,21 +117,6 @@ extension APIService {
)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error> in
self.backgroundManagedObjectContext.performChanges {
let blockedDomain: DomainBlock? = {
let request = DomainBlock.sortedFetchRequest
request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID, blockedDomain: user.domainFromAcct)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
return try self.backgroundManagedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}()
if let blockedDomain = blockedDomain {
self.backgroundManagedObjectContext.delete(blockedDomain)
}
let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: authorizationBox.domain, id: authorizationBox.userID)
requestMastodonUserRequest.fetchLimit = 1

View File

@ -5,34 +5,56 @@
// Created by sxiaojian on 2021/4/29.
//
import Combine
import CoreData
import CoreDataStack
import Foundation
import Combine
import MastodonSDK
import OSLog
import UIKit
final class BlockDomainService {
let userProvider: UserProvider
let cell: UITableViewCell?
let indexPath: IndexPath?
init(userProvider: UserProvider,
cell: UITableViewCell?,
indexPath: IndexPath?
// input
weak var backgroundManagedObjectContext: NSManagedObjectContext?
weak var authenticationService: AuthenticationService?
// output
let blockedDomains = CurrentValueSubject<[String], Never>([])
init(
backgroundManagedObjectContext: NSManagedObjectContext,
authenticationService: AuthenticationService
) {
self.userProvider = userProvider
self.cell = cell
self.indexPath = indexPath
self.backgroundManagedObjectContext = backgroundManagedObjectContext
self.authenticationService = authenticationService
guard let authorizationBox = authenticationService.activeMastodonAuthenticationBox.value else { return }
backgroundManagedObjectContext.perform {
let _blockedDomains: [DomainBlock] = {
let request = DomainBlock.sortedFetchRequest
request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID)
request.returnsObjectsAsFaults = false
do {
return try backgroundManagedObjectContext.fetch(request)
} catch {
assertionFailure(error.localizedDescription)
return []
}
}()
self.blockedDomains.value = _blockedDomains.map(\.blockedDomain)
}
}
func blockDomain() {
guard let activeMastodonAuthenticationBox = self.userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let context = self.userProvider.context else {
func blockDomain(
userProvider: UserProvider,
cell: UITableViewCell?,
indexPath: IndexPath?
) {
guard let activeMastodonAuthenticationBox = userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let context = userProvider.context else {
return
}
var mastodonUser: AnyPublisher<MastodonUser?, Never>
if let cell = self.cell, let indexPath = self.indexPath {
if let cell = cell, let indexPath = indexPath {
mastodonUser = userProvider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher()
} else {
mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher()
@ -45,8 +67,8 @@ final class BlockDomainService {
return context.apiService.blockDomain(user: mastodonUser, authorizationBox: activeMastodonAuthenticationBox)
}
.switchToLatest()
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> in
return context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox)
.flatMap { _ -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> in
context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox)
}
.sink { completion in
switch completion {
@ -55,19 +77,23 @@ final class BlockDomainService {
case .failure(let error):
print(error)
}
} receiveValue: { response in
print(response)
} receiveValue: { [weak self] response in
self?.blockedDomains.value = response.value
}
.store(in: &userProvider.disposeBag)
}
func unblockDomain() {
guard let activeMastodonAuthenticationBox = self.userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let context = self.userProvider.context else {
func unblockDomain(
userProvider: UserProvider,
cell: UITableViewCell?,
indexPath: IndexPath?
) {
guard let activeMastodonAuthenticationBox = userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let context = userProvider.context else {
return
}
var mastodonUser: AnyPublisher<MastodonUser?, Never>
if let cell = self.cell, let indexPath = self.indexPath {
if let cell = cell, let indexPath = indexPath {
mastodonUser = userProvider.mastodonUser(for: cell, indexPath: indexPath).eraseToAnyPublisher()
} else {
mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher()
@ -80,8 +106,8 @@ final class BlockDomainService {
return context.apiService.unblockDomain(user: mastodonUser, authorizationBox: activeMastodonAuthenticationBox)
}
.switchToLatest()
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> in
return context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox)
.flatMap { _ -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> in
context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox)
}
.sink { completion in
switch completion {
@ -90,13 +116,9 @@ final class BlockDomainService {
case .failure(let error):
print(error)
}
} receiveValue: { response in
print(response)
} receiveValue: { [weak self] response in
self?.blockedDomains.value = response.value
}
.store(in: &userProvider.disposeBag)
}
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
}
}

View File

@ -30,6 +30,7 @@ class AppContext: ObservableObject {
let statusPublishService = StatusPublishService()
let notificationService: NotificationService
let settingService: SettingService
let blockDomainService: BlockDomainService
let documentStore: DocumentStore
private var documentStoreSubscription: AnyCancellable!
@ -72,6 +73,11 @@ class AppContext: ObservableObject {
notificationService: _notificationService
)
blockDomainService = BlockDomainService(
backgroundManagedObjectContext: _backgroundManagedObjectContext,
authenticationService: _authenticationService
)
documentStore = DocumentStore()
documentStoreSubscription = documentStore.objectWillChange
.receive(on: DispatchQueue.main)

View File

@ -85,7 +85,7 @@ extension Mastodon.API.DomainBlock {
session: URLSession,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error> {
let query = Mastodon.API.DomainBlock.BlockQuery(domain: blockDomain)
let query = Mastodon.API.DomainBlock.BlockDeleteQuery(domain: blockDomain)
let request = Mastodon.API.delete(
url: domainBlockEndpointURL(domain: domain),
query: query,
@ -126,7 +126,17 @@ extension Mastodon.API.DomainBlock {
}
}
public struct BlockDeleteQuery: Codable, DeleteQuery {
public let domain: String
public init(domain: String) {
self.domain = domain
}
}
public struct BlockQuery: Codable, PostQuery {
public let domain: String
public init(domain: String) {

View File

@ -142,14 +142,6 @@ extension Mastodon.API {
) -> URLRequest {
return buildRequest(url: url, method: .POST, query: query, authorization: authorization)
}
static func delete(
url: URL,
query: PostQuery?,
authorization: OAuth.Authorization?
) -> URLRequest {
return buildRequest(url: url, method: .DELETE, query: query, authorization: authorization)
}
static func patch(
url: URL,

View File

@ -11,5 +11,4 @@ extension Mastodon.Entity {
public struct Empty: Codable {
}
}

View File

@ -60,3 +60,8 @@ protocol PutQuery: RequestQuery { }
// DELETE
protocol DeleteQuery: RequestQuery { }
extension DeleteQuery {
// By default a `PostQuery` does not has query items
var queryItems: [URLQueryItem]? { nil }
}