Use relationships in Report-process (IOS-192)

This commit is contained in:
Nathan Mattes 2023-12-29 18:06:48 +01:00
parent bb3ad77954
commit 47986262bc
10 changed files with 43 additions and 318 deletions

View File

@ -502,9 +502,7 @@ private extension SceneCoordinator {
_viewController.viewModel = viewModel
viewController = _viewController
case .report(let viewModel):
let _viewController = ReportViewController()
_viewController.viewModel = viewModel
viewController = _viewController
viewController = ReportViewController(viewModel: viewModel)
case .reportServerRules(let viewModel):
let _viewController = ReportServerRulesViewController()
_viewController.viewModel = viewModel

View File

@ -244,10 +244,13 @@ extension DataSourceFacade {
case .reportUser:
Task {
guard let relationship = try? await dependency.context.apiService.relationship(forAccounts: [menuContext.author], authenticationBox: dependency.authContext.mastodonAuthenticationBox).value.first else { return }
let reportViewModel = ReportViewModel(
context: dependency.context,
authContext: dependency.authContext,
account: menuContext.author,
relationship: relationship,
status: menuContext.statusViewModel?.originalStatus
)

View File

@ -20,19 +20,22 @@ class ReportViewController: UIViewController, NeedsDependency, ReportViewControl
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: ReportViewModel!
let viewModel: ReportViewModel
lazy var cancelBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .cancel,
target: self,
action: #selector(ReportViewController.cancelBarButtonItemDidPressed(_:))
)
}
init(viewModel: ReportViewModel) {
self.viewModel = viewModel
extension ReportViewController {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func viewDidLoad() {
super.viewDidLoad()
@ -46,11 +49,10 @@ extension ReportViewController {
viewModel.reportStatusViewModel.delegate = self
viewModel.reportSupplementaryViewModel.delegate = self
let reportReasonViewController = ReportReasonViewController()
let reportReasonViewController = ReportReasonViewController(viewModel: viewModel.reportReasonViewModel)
reportReasonViewController.context = context
reportReasonViewController.coordinator = coordinator
reportReasonViewController.viewModel = viewModel.reportReasonViewModel
addChild(reportReasonViewController)
reportReasonViewController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(reportReasonViewController.view)
@ -58,10 +60,6 @@ extension ReportViewController {
reportReasonViewController.view.pinToParent()
}
}
extension ReportViewController {
@objc private func cancelBarButtonItemDidPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
}
@ -85,6 +83,7 @@ extension ReportViewController: ReportReasonViewControllerDelegate {
context: context,
authContext: viewModel.authContext,
account: viewModel.account,
relationship: viewModel.relationship,
isReported: false
)
_ = coordinator.present(
@ -156,11 +155,12 @@ extension ReportViewController: ReportSupplementaryViewControllerDelegate {
Task { @MainActor in
do {
let _ = try await viewModel.report()
let reportResultViewModel = ReportResultViewModel(
context: context,
authContext: viewModel.authContext,
account: viewModel.account,
relationship: viewModel.relationship,
isReported: true
)

View File

@ -29,6 +29,7 @@ class ReportViewModel {
let context: AppContext
let authContext: AuthContext
let account: Mastodon.Entity.Account
let relationship: Mastodon.Entity.Relationship
let status: MastodonStatus?
// output
@ -40,11 +41,13 @@ class ReportViewModel {
context: AppContext,
authContext: AuthContext,
account: Mastodon.Entity.Account,
relationship: Mastodon.Entity.Relationship,
status: MastodonStatus?
) {
self.context = context
self.authContext = authContext
self.account = account
self.relationship = relationship
self.status = status
self.reportReasonViewModel = ReportReasonViewModel(context: context)
self.reportServerRulesViewModel = ReportServerRulesViewModel(context: context)

View File

@ -25,9 +25,9 @@ final class ReportReasonViewController: UIViewController, NeedsDependency, Repor
var disposeBag = Set<AnyCancellable>()
private var observations = Set<NSKeyValueObservation>()
var viewModel: ReportReasonViewModel!
private(set) lazy var reportReasonView = ReportReasonView(viewModel: viewModel)
let viewModel: ReportReasonViewModel
let reportReasonView: ReportReasonView
let navigationActionView: NavigationActionView = {
let navigationActionView = NavigationActionView()
navigationActionView.backgroundColor = Asset.Scene.Onboarding.background.color
@ -35,10 +35,14 @@ final class ReportReasonViewController: UIViewController, NeedsDependency, Repor
return navigationActionView
}()
init(viewModel: ReportReasonViewModel) {
self.viewModel = viewModel
reportReasonView = ReportReasonView(viewModel: viewModel)
super.init(nibName: nil, bundle: nil)
}
}
extension ReportReasonViewController {
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func viewDidLoad() {
super.viewDidLoad()

View File

@ -75,7 +75,7 @@ struct ReportResultView: View {
action: {
viewModel.followActionPublisher.send()
},
title: viewModel.relationshipViewModel.isFollowing ? L10n.Scene.Report.StepFinal.unfollow : L10n.Scene.Report.StepFinal.unfollowed,
title: viewModel.relationship.following ? L10n.Scene.Report.StepFinal.unfollow : L10n.Scene.Report.StepFinal.unfollowed,
isBusy: viewModel.isRequestFollow
)
}
@ -92,7 +92,7 @@ struct ReportResultView: View {
action: {
viewModel.muteActionPublisher.send()
},
title: viewModel.relationshipViewModel.isMuting ? L10n.Common.Controls.Friendship.muted : L10n.Common.Controls.Friendship.mute,
title: viewModel.relationship.muting ? L10n.Common.Controls.Friendship.muted : L10n.Common.Controls.Friendship.mute,
isBusy: viewModel.isRequestMute
)
}
@ -109,7 +109,7 @@ struct ReportResultView: View {
action: {
viewModel.blockActionPublisher.send()
},
title: viewModel.relationshipViewModel.isBlocking ? L10n.Common.Controls.Friendship.blocked : L10n.Common.Controls.Friendship.block,
title: viewModel.relationship.blocking ? L10n.Common.Controls.Friendship.blocked : L10n.Common.Controls.Friendship.block,
isBusy: viewModel.isRequestBlock
)
}

View File

@ -88,10 +88,11 @@ extension ReportResultViewController {
guard !self.viewModel.isRequestFollow else { return }
self.viewModel.isRequestFollow = true
do {
try await DataSourceFacade.responseToUserFollowAction(
let newRelationship = try await DataSourceFacade.responseToUserFollowAction(
dependency: self,
account: self.viewModel.account
)
self.viewModel.relationship = newRelationship
} catch {
// handle error
}
@ -108,10 +109,11 @@ extension ReportResultViewController {
guard !self.viewModel.isRequestMute else { return }
self.viewModel.isRequestMute = true
do {
_ = try await DataSourceFacade.responseToUserMuteAction(
let newRelationship = try await DataSourceFacade.responseToUserMuteAction(
dependency: self,
account: self.viewModel.account
)
self.viewModel.relationship = newRelationship
} catch {
// handle error
}
@ -128,10 +130,11 @@ extension ReportResultViewController {
guard !self.viewModel.isRequestBlock else { return }
self.viewModel.isRequestBlock = true
do {
_ = try await DataSourceFacade.responseToUserBlockAction(
let newRelationship = try await DataSourceFacade.responseToUserBlockAction(
dependency: self,
account: self.viewModel.account
)
self.viewModel.relationship = newRelationship
} catch {
// handle error
}

View File

@ -24,6 +24,7 @@ class ReportResultViewModel: ObservableObject {
let context: AppContext
let authContext: AuthContext
let account: Mastodon.Entity.Account
var relationship: Mastodon.Entity.Relationship
let isReported: Bool
var headline: String {
@ -39,8 +40,7 @@ class ReportResultViewModel: ObservableObject {
// output
@Published var avatarURL: URL?
@Published var username: String = ""
let relationshipViewModel = RelationshipViewModel()
let muteActionPublisher = PassthroughSubject<Void, Never>()
let followActionPublisher = PassthroughSubject<Void, Never>()
let blockActionPublisher = PassthroughSubject<Void, Never>()
@ -49,11 +49,13 @@ class ReportResultViewModel: ObservableObject {
context: AppContext,
authContext: AuthContext,
account: Mastodon.Entity.Account,
relationship: Mastodon.Entity.Relationship,
isReported: Bool
) {
self.context = context
self.authContext = authContext
self.account = account
self.relationship = relationship
self.isReported = isReported
// end init

View File

@ -94,25 +94,6 @@ extension ProfileRelationshipActionButton {
}
}
public func configure(actionOptionSet: RelationshipActionOptionSet) {
setTitle(actionOptionSet.title, for: .normal)
configureAppearance()
titleEdgeInsets = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 4)
activityIndicatorView.stopAnimating()
if let option = actionOptionSet.highPriorityAction(except: .editOptions), option == .blocked || option == .suspended {
isEnabled = false
} else if actionOptionSet.contains(.updating) {
isEnabled = false
activityIndicatorView.startAnimating()
} else {
isEnabled = true
}
}
private func configureAppearance() {
setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal)
setTitleColor(Asset.Colors.Label.primaryReverse.color.withAlphaComponent(0.5), for: .highlighted)

View File

@ -1,269 +0,0 @@
//
// RelationshipViewModel.swift
//
//
// Created by MainasuK on 2022-4-14.
//
import UIKit
import Combine
import MastodonAsset
import MastodonLocalization
import CoreDataStack
public enum RelationshipAction: Int, CaseIterable {
case showReblogs
case isMyself
case followingBy
case blockingBy
case none // set hide from UI
case follow
case request
case pending
case following
case muting
case blocked
case blocking
case suspended
case edit
case editing
case updating
public var option: RelationshipActionOptionSet {
return RelationshipActionOptionSet(rawValue: 1 << rawValue)
}
}
// construct option set on the enum for safe iterator
public struct RelationshipActionOptionSet: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
public static let isMyself = RelationshipAction.isMyself.option
public static let followingBy = RelationshipAction.followingBy.option
public static let blockingBy = RelationshipAction.blockingBy.option
public static let none = RelationshipAction.none.option
public static let follow = RelationshipAction.follow.option
public static let request = RelationshipAction.request.option
public static let pending = RelationshipAction.pending.option
public static let following = RelationshipAction.following.option
public static let muting = RelationshipAction.muting.option
public static let blocked = RelationshipAction.blocked.option
public static let blocking = RelationshipAction.blocking.option
public static let suspended = RelationshipAction.suspended.option
public static let edit = RelationshipAction.edit.option
public static let editing = RelationshipAction.editing.option
public static let updating = RelationshipAction.updating.option
public static let showReblogs = RelationshipAction.showReblogs.option
public static let editOptions: RelationshipActionOptionSet = [.edit, .editing, .updating]
public func highPriorityAction(except: RelationshipActionOptionSet) -> RelationshipAction? {
let set = subtracting(except)
for action in RelationshipAction.allCases.reversed() where set.contains(action.option) {
return action
}
return nil
}
public var title: String {
guard let highPriorityAction = self.highPriorityAction(except: []) else {
assertionFailure()
return " "
}
switch highPriorityAction {
case .isMyself: return ""
case .followingBy: return " "
case .blockingBy: return " "
case .none: return " "
case .follow: return L10n.Common.Controls.Friendship.follow
case .request: return L10n.Common.Controls.Friendship.request
case .pending: return L10n.Common.Controls.Friendship.pending
case .following: return L10n.Common.Controls.Friendship.following
case .muting: return L10n.Common.Controls.Friendship.muted
case .blocked: return L10n.Common.Controls.Friendship.follow // blocked by user (deprecated)
case .blocking: return L10n.Common.Controls.Friendship.blocked
case .suspended: return L10n.Common.Controls.Friendship.follow
case .edit: return L10n.Common.Controls.Friendship.editInfo
case .editing: return L10n.Common.Controls.Actions.done
case .updating: return " "
case .showReblogs: return " "
}
}
}
@available(*, deprecated, message: "Replace with Mastodon.Entity.Relationship")
public final class RelationshipViewModel {
var disposeBag = Set<AnyCancellable>()
public var userObserver: AnyCancellable?
public var meObserver: AnyCancellable?
// input
@Published public var user: MastodonUser?
@Published public var me: MastodonUser?
public let relationshipUpdatePublisher = CurrentValueSubject<Void, Never>(Void()) // needs initial event
// output
@Published public var isMyself = false
@Published public var optionSet: RelationshipActionOptionSet?
@Published public var isFollowing = false
@Published public var isFollowingBy = false
@Published public var isMuting = false
@Published public var showReblogs = false
@Published public var isBlocking = false
@Published public var isBlockingBy = false
@Published public var isSuspended = false
public init() {
Publishers.CombineLatest3(
$user,
$me,
relationshipUpdatePublisher
)
.receive(on: DispatchQueue.main)
.sink { [weak self] user, me, _ in
guard let self = self else { return }
self.update(user: user, me: me)
guard let user = user, let me = me else {
self.userObserver = nil
self.meObserver = nil
return
}
// do not modify object to prevent infinity loop
self.userObserver = RelationshipViewModel.createObjectChangePublisher(user: user)
.sink { [weak self] _ in
guard let self = self else { return }
self.relationshipUpdatePublisher.send()
}
self.meObserver = RelationshipViewModel.createObjectChangePublisher(user: me)
.sink { [weak self] _ in
guard let self = self else { return }
self.relationshipUpdatePublisher.send()
}
}
.store(in: &disposeBag)
}
}
extension RelationshipViewModel {
public static func createObjectChangePublisher(user: MastodonUser) -> AnyPublisher<Void, Never> {
return ManagedObjectObserver
.observe(object: user)
.map { _ in Void() }
.catch { error in
return Just(Void())
}
.eraseToAnyPublisher()
}
}
extension RelationshipViewModel {
private func update(user: MastodonUser?, me: MastodonUser?) {
guard let user = user,
let me = me
else {
reset()
return
}
let optionSet = RelationshipViewModel.optionSet(user: user, me: me)
self.isMyself = optionSet.contains(.isMyself)
self.isFollowingBy = optionSet.contains(.followingBy)
self.isFollowing = optionSet.contains(.following)
self.isMuting = optionSet.contains(.muting)
self.isBlockingBy = optionSet.contains(.blockingBy)
self.isBlocking = optionSet.contains(.blocking)
self.isSuspended = optionSet.contains(.suspended)
self.showReblogs = optionSet.contains(.showReblogs)
self.optionSet = optionSet
}
private func reset() {
isMyself = false
isFollowingBy = false
isFollowing = false
isMuting = false
isBlockingBy = false
isBlocking = false
optionSet = nil
showReblogs = false
}
}
extension RelationshipViewModel {
public static func optionSet(user: MastodonUser, me: MastodonUser) -> RelationshipActionOptionSet {
let isMyself = user.id == me.id && user.domain == me.domain
guard !isMyself else {
return [.isMyself, .edit]
}
let isProtected = user.locked
let isFollowingBy = me.followingBy.contains(user)
let isFollowing = user.followingBy.contains(me)
let isPending = user.followRequestedBy.contains(me)
let isMuting = user.mutingBy.contains(me)
let isBlockingBy = me.blockingBy.contains(user)
let isBlocking = user.blockingBy.contains(me)
let isShowingReblogs = me.showingReblogsBy.contains(user)
var optionSet: RelationshipActionOptionSet = [.follow]
if isMyself {
optionSet.insert(.isMyself)
}
if isProtected {
optionSet.insert(.request)
}
if isFollowingBy {
optionSet.insert(.followingBy)
}
if isFollowing {
optionSet.insert(.following)
}
if isPending {
optionSet.insert(.pending)
}
if isMuting {
optionSet.insert(.muting)
}
if isBlockingBy {
optionSet.insert(.blockingBy)
}
if isBlocking {
optionSet.insert(.blocking)
}
if user.suspended {
optionSet.insert(.suspended)
}
if isShowingReblogs {
optionSet.insert(.showReblogs)
}
return optionSet
}
}