// // 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 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 case showReblogs 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 "" } } } 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 ) .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)// user.showingReblogsBy.contains(me) 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 } }