diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 8f1c9cc3d..ef7f3b9cf 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -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 diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index 8ab436994..2595da4dc 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -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 ) diff --git a/Mastodon/Scene/Report/Report/ReportViewController.swift b/Mastodon/Scene/Report/Report/ReportViewController.swift index ae4c3448f..67b8046a8 100644 --- a/Mastodon/Scene/Report/Report/ReportViewController.swift +++ b/Mastodon/Scene/Report/Report/ReportViewController.swift @@ -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 ) diff --git a/Mastodon/Scene/Report/Report/ReportViewModel.swift b/Mastodon/Scene/Report/Report/ReportViewModel.swift index 1a21885f9..cff16063a 100644 --- a/Mastodon/Scene/Report/Report/ReportViewModel.swift +++ b/Mastodon/Scene/Report/Report/ReportViewModel.swift @@ -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) diff --git a/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift b/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift index 2bfb689d0..2e89229ca 100644 --- a/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift +++ b/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift @@ -25,9 +25,9 @@ final class ReportReasonViewController: UIViewController, NeedsDependency, Repor var disposeBag = Set() private var observations = Set() - 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() diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultView.swift b/Mastodon/Scene/Report/ReportResult/ReportResultView.swift index 361f5db24..0805cb315 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultView.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultView.swift @@ -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 ) } diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift index 77935cb9a..62b82e8a1 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift @@ -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 } diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift index 70853e184..c7688963b 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift @@ -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() let followActionPublisher = PassthroughSubject() let blockActionPublisher = PassthroughSubject() @@ -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 diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift index 892a82b0b..eaf484dae 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift @@ -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) diff --git a/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift b/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift deleted file mode 100644 index f24e1b47f..000000000 --- a/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift +++ /dev/null @@ -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() - - public var userObserver: AnyCancellable? - public var meObserver: AnyCancellable? - - // input - @Published public var user: MastodonUser? - @Published public var me: MastodonUser? - public let relationshipUpdatePublisher = CurrentValueSubject(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 { - 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 - } -}