2022-04-14 15:15:21 +02:00
|
|
|
//
|
|
|
|
// 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
|
2022-11-06 09:25:26 +01:00
|
|
|
case showReblogs
|
2022-04-14 15:15:21 +02:00
|
|
|
|
|
|
|
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
|
2022-11-06 09:25:26 +01:00
|
|
|
public static let showReblogs = RelationshipAction.showReblogs.option
|
2022-04-14 15:15:21 +02:00
|
|
|
|
|
|
|
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 {
|
2022-11-06 09:25:26 +01:00
|
|
|
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 " "
|
2022-11-06 10:16:56 +01:00
|
|
|
case .showReblogs: return " "
|
2022-04-14 15:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2022-11-03 17:17:02 +01:00
|
|
|
@Published public var showReblogs = false
|
2022-04-14 15:15:21 +02:00
|
|
|
@Published public var isBlocking = false
|
|
|
|
@Published public var isBlockingBy = false
|
2022-05-26 17:19:47 +02:00
|
|
|
@Published public var isSuspended = false
|
2022-04-14 15:15:21 +02:00
|
|
|
|
|
|
|
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<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)
|
2022-05-26 17:19:47 +02:00
|
|
|
self.isSuspended = optionSet.contains(.suspended)
|
2022-11-06 09:25:26 +01:00
|
|
|
self.showReblogs = optionSet.contains(.showReblogs)
|
2022-04-14 15:15:21 +02:00
|
|
|
|
|
|
|
self.optionSet = optionSet
|
|
|
|
}
|
|
|
|
|
|
|
|
private func reset() {
|
|
|
|
isMyself = false
|
|
|
|
isFollowingBy = false
|
|
|
|
isFollowing = false
|
|
|
|
isMuting = false
|
|
|
|
isBlockingBy = false
|
|
|
|
isBlocking = false
|
|
|
|
optionSet = nil
|
2022-11-06 09:25:26 +01:00
|
|
|
showReblogs = false
|
2022-04-14 15:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension RelationshipViewModel {
|
|
|
|
|
|
|
|
public static func optionSet(user: MastodonUser, me: MastodonUser) -> RelationshipActionOptionSet {
|
|
|
|
let isMyself = user.id == me.id && user.domain == me.domain
|
|
|
|
guard !isMyself else {
|
2022-05-26 17:19:47 +02:00
|
|
|
return [.isMyself, .edit]
|
2022-04-14 15:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2022-11-06 10:16:56 +01:00
|
|
|
let isShowingReblogs = me.showingReblogsBy.contains(user)
|
2022-04-14 15:15:21 +02:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
2022-05-26 17:19:47 +02:00
|
|
|
|
|
|
|
if user.suspended {
|
|
|
|
optionSet.insert(.suspended)
|
|
|
|
}
|
2022-11-06 09:25:26 +01:00
|
|
|
|
|
|
|
if isShowingReblogs {
|
|
|
|
optionSet.insert(.showReblogs)
|
|
|
|
}
|
2022-04-14 15:15:21 +02:00
|
|
|
|
|
|
|
return optionSet
|
|
|
|
}
|
|
|
|
}
|