mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
365 lines
14 KiB
Swift
365 lines
14 KiB
Swift
import Foundation
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import AccountContext
|
|
import TemporaryCachedPeerDataManager
|
|
|
|
enum PeerInfoMemberRole {
|
|
case creator
|
|
case admin
|
|
case member
|
|
}
|
|
|
|
enum PeerInfoMember: Equatable {
|
|
case channelMember(participant: RenderedChannelParticipant, storyStats: PeerStoryStats?)
|
|
case legacyGroupMember(peer: RenderedPeer, role: PeerInfoMemberRole, invitedBy: PeerId?, presence: TelegramUserPresence?, storyStats: PeerStoryStats?)
|
|
case account(peer: RenderedPeer)
|
|
|
|
var id: PeerId {
|
|
switch self {
|
|
case let .channelMember(participant, _):
|
|
return participant.peer.id
|
|
case let .legacyGroupMember(peer, _, _, _, _):
|
|
return peer.peerId
|
|
case let .account(peer):
|
|
return peer.peerId
|
|
}
|
|
}
|
|
|
|
var peer: Peer {
|
|
switch self {
|
|
case let .channelMember(participant, _):
|
|
return participant.peer
|
|
case let .legacyGroupMember(peer, _, _, _, _):
|
|
return peer.peers[peer.peerId]!
|
|
case let .account(peer):
|
|
return peer.peers[peer.peerId]!
|
|
}
|
|
}
|
|
|
|
var presence: TelegramUserPresence? {
|
|
switch self {
|
|
case let .channelMember(participant, _):
|
|
return participant.presences[participant.peer.id] as? TelegramUserPresence
|
|
case let .legacyGroupMember(_, _, _, presence, _):
|
|
return presence
|
|
case .account:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var role: PeerInfoMemberRole {
|
|
switch self {
|
|
case let .channelMember(participant, _):
|
|
switch participant.participant {
|
|
case .creator:
|
|
return .creator
|
|
case let .member(_, _, adminInfo, _, _):
|
|
if adminInfo != nil {
|
|
return .admin
|
|
} else {
|
|
return .member
|
|
}
|
|
}
|
|
case let .legacyGroupMember(_, role, _, _, _):
|
|
return role
|
|
case .account:
|
|
return .member
|
|
}
|
|
}
|
|
|
|
var rank: String? {
|
|
switch self {
|
|
case let .channelMember(participant, _):
|
|
switch participant.participant {
|
|
case let .creator(_, _, rank):
|
|
return rank
|
|
case let .member(_, _, _, _, rank):
|
|
return rank
|
|
}
|
|
case .legacyGroupMember:
|
|
return nil
|
|
case .account:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var storyStats: PeerStoryStats? {
|
|
switch self {
|
|
case let .channelMember(_, value):
|
|
return value
|
|
case let .legacyGroupMember(_, _, _, _, value):
|
|
return value
|
|
case .account:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
enum PeerInfoMembersDataState: Equatable {
|
|
case loading(isInitial: Bool)
|
|
case ready(canLoadMore: Bool)
|
|
}
|
|
|
|
struct PeerInfoMembersState: Equatable {
|
|
var canAddMembers: Bool
|
|
var members: [PeerInfoMember]
|
|
var dataState: PeerInfoMembersDataState
|
|
}
|
|
|
|
private func membersSortedByPresence(_ members: [PeerInfoMember], accountPeerId: PeerId) -> [PeerInfoMember] {
|
|
return members.sorted(by: { lhs, rhs in
|
|
if lhs.id == accountPeerId {
|
|
return true
|
|
} else if rhs.id == accountPeerId {
|
|
return false
|
|
}
|
|
|
|
let lhsPresence = lhs.presence
|
|
let rhsPresence = rhs.presence
|
|
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
|
if lhsPresence.status < rhsPresence.status {
|
|
return false
|
|
} else if lhsPresence.status > rhsPresence.status {
|
|
return true
|
|
}
|
|
} else if let _ = lhsPresence {
|
|
return true
|
|
} else if let _ = rhsPresence {
|
|
return false
|
|
}
|
|
return lhs.id < rhs.id
|
|
})
|
|
}
|
|
|
|
private final class PeerInfoMembersContextImpl {
|
|
private let queue: Queue
|
|
private let context: AccountContext
|
|
private let peerId: PeerId
|
|
|
|
private var canAddMembers = false
|
|
private var members: [PeerInfoMember] = []
|
|
private var dataState: PeerInfoMembersDataState = .loading(isInitial: true)
|
|
private var removingMemberIds: [PeerId: Disposable] = [:]
|
|
private var membersHidden: Bool?
|
|
|
|
private let stateValue = Promise<PeerInfoMembersState>()
|
|
var state: Signal<PeerInfoMembersState, NoError> {
|
|
return self.stateValue.get()
|
|
}
|
|
private let disposable = MetaDisposable()
|
|
private let peerDisposable = MetaDisposable()
|
|
private var channelMembersControl: PeerChannelMemberCategoryControl?
|
|
|
|
init(queue: Queue, context: AccountContext, peerId: PeerId) {
|
|
self.queue = queue
|
|
self.context = context
|
|
self.peerId = peerId
|
|
|
|
self.pushState()
|
|
|
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
let (disposable, control) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, requestUpdate: true, updated: { [weak self] state in
|
|
queue.async {
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let unsortedMembers = state.list.map { item -> PeerInfoMember in
|
|
return .channelMember(participant: item, storyStats: state.peerStoryStats[item.peer.id])
|
|
}
|
|
let members: [PeerInfoMember]
|
|
if unsortedMembers.count <= 50 {
|
|
members = membersSortedByPresence(unsortedMembers, accountPeerId: strongSelf.context.account.peerId)
|
|
} else {
|
|
members = unsortedMembers
|
|
}
|
|
strongSelf.members = members
|
|
switch state.loadingState {
|
|
case let .loading(initial):
|
|
strongSelf.dataState = .loading(isInitial: initial)
|
|
case let .ready(hasMore):
|
|
strongSelf.dataState = .ready(canLoadMore: hasMore)
|
|
}
|
|
strongSelf.pushState()
|
|
}
|
|
})
|
|
self.disposable.set(disposable)
|
|
self.channelMembersControl = control
|
|
|
|
self.peerDisposable.set((context.account.postbox.peerView(id: peerId)
|
|
|> deliverOn(self.queue)).start(next: { [weak self] view in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
if let channel = peerViewMainPeer(view) as? TelegramChannel {
|
|
var canAddMembers = false
|
|
switch channel.info {
|
|
case .broadcast:
|
|
break
|
|
case .group:
|
|
if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
|
|
canAddMembers = true
|
|
}
|
|
}
|
|
strongSelf.canAddMembers = canAddMembers
|
|
strongSelf.pushState()
|
|
}
|
|
|
|
var membersHidden: Bool?
|
|
if let cachedData = view.cachedData as? CachedChannelData, case let .known(value) = cachedData.membersHidden {
|
|
membersHidden = value.value
|
|
}
|
|
|
|
if strongSelf.membersHidden != membersHidden {
|
|
let shouldResetList = strongSelf.membersHidden != nil
|
|
strongSelf.membersHidden = membersHidden
|
|
|
|
if shouldResetList, let control = strongSelf.channelMembersControl {
|
|
context.peerChannelMemberCategoriesContextsManager.reset(peerId: peerId, control: control)
|
|
}
|
|
}
|
|
}))
|
|
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
|
|
self.disposable.set((context.account.postbox.peerView(id: peerId)
|
|
|> deliverOn(self.queue)).start(next: { [weak self] view in
|
|
guard let strongSelf = self, let cachedData = view.cachedData as? CachedGroupData, let participantsData = cachedData.participants else {
|
|
return
|
|
}
|
|
var unsortedMembers: [PeerInfoMember] = []
|
|
for participant in participantsData.participants {
|
|
if let peer = view.peers[participant.peerId] {
|
|
let role: PeerInfoMemberRole
|
|
let invitedBy: PeerId?
|
|
switch participant {
|
|
case .creator:
|
|
role = .creator
|
|
invitedBy = nil
|
|
case let .admin(_, invitedByValue, _):
|
|
role = .admin
|
|
invitedBy = invitedByValue
|
|
case let .member(_, invitedByValue, _):
|
|
role = .member
|
|
invitedBy = invitedByValue
|
|
}
|
|
unsortedMembers.append(.legacyGroupMember(peer: RenderedPeer(peer: peer), role: role, invitedBy: invitedBy, presence: view.peerPresences[participant.peerId] as? TelegramUserPresence, storyStats: view.memberStoryStats[participant.peerId]))
|
|
}
|
|
}
|
|
|
|
if let group = peerViewMainPeer(view) as? TelegramGroup {
|
|
var canAddMembers = false
|
|
switch group.role {
|
|
case .admin, .creator:
|
|
canAddMembers = true
|
|
case .member:
|
|
break
|
|
}
|
|
if !group.hasBannedPermission(.banAddMembers) {
|
|
canAddMembers = true
|
|
}
|
|
strongSelf.canAddMembers = canAddMembers
|
|
}
|
|
|
|
strongSelf.members = membersSortedByPresence(unsortedMembers, accountPeerId: strongSelf.context.account.peerId)
|
|
strongSelf.dataState = .ready(canLoadMore: false)
|
|
strongSelf.pushState()
|
|
}))
|
|
} else {
|
|
self.dataState = .ready(canLoadMore: false)
|
|
self.pushState()
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.disposable.dispose()
|
|
self.peerDisposable.dispose()
|
|
}
|
|
|
|
private func pushState() {
|
|
if self.removingMemberIds.isEmpty {
|
|
self.stateValue.set(.single(PeerInfoMembersState(canAddMembers: self.canAddMembers, members: self.members, dataState: self.dataState)))
|
|
} else {
|
|
self.stateValue.set(.single(PeerInfoMembersState(canAddMembers: self.canAddMembers, members: self.members.filter { member in
|
|
return self.removingMemberIds[member.id] == nil
|
|
}, dataState: self.dataState)))
|
|
}
|
|
}
|
|
|
|
func loadMore() {
|
|
if case .ready(true) = self.dataState, let channelMembersControl = self.channelMembersControl {
|
|
self.context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: self.peerId, control: channelMembersControl)
|
|
}
|
|
}
|
|
|
|
func removeMember(memberId: PeerId) {
|
|
if removingMemberIds[memberId] == nil {
|
|
let signal: Signal<Never, NoError>
|
|
if self.peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
signal = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: self.context.engine, peerId: self.peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
|
|> ignoreValues
|
|
} else {
|
|
signal = self.context.engine.peers.removePeerMember(peerId: self.peerId, memberId: memberId)
|
|
|> ignoreValues
|
|
}
|
|
let completed: () -> Void = { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
if let _ = strongSelf.removingMemberIds.removeValue(forKey: memberId) {
|
|
strongSelf.pushState()
|
|
}
|
|
}
|
|
let disposable = MetaDisposable()
|
|
self.removingMemberIds[memberId] = disposable
|
|
|
|
self.pushState()
|
|
|
|
disposable.set((signal
|
|
|> deliverOn(self.queue)).start(completed: {
|
|
completed()
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
final class PeerInfoMembersContext: Equatable {
|
|
private let queue = Queue.mainQueue()
|
|
private let impl: QueueLocalObject<PeerInfoMembersContextImpl>
|
|
|
|
var state: Signal<PeerInfoMembersState, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
self.impl.with { impl in
|
|
disposable.set(impl.state.start(next: { value in
|
|
subscriber.putNext(value)
|
|
}))
|
|
}
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
init(context: AccountContext, peerId: PeerId) {
|
|
let queue = self.queue
|
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
|
return PeerInfoMembersContextImpl(queue: queue, context: context, peerId: peerId)
|
|
})
|
|
}
|
|
|
|
func loadMore() {
|
|
self.impl.with { impl in
|
|
impl.loadMore()
|
|
}
|
|
}
|
|
|
|
func removeMember(memberId: PeerId) {
|
|
self.impl.with { impl in
|
|
impl.removeMember(memberId: memberId)
|
|
}
|
|
}
|
|
|
|
static func ==(lhs: PeerInfoMembersContext, rhs: PeerInfoMembersContext) -> Bool {
|
|
return lhs === rhs
|
|
}
|
|
}
|