PeerInfoMembersContext
enum
PeerInfoMemberRole
1
2
3
case creator
case admin
case member
enum
PeerInfoMember
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
case channelMember(RenderedChannelParticipant)
case legacyGroupMember(peer: RenderedPeer, role: PeerInfoMemberRole, invitedBy: PeerId?, presence: TelegramUserPresence?)
case account(peer: RenderedPeer)
var id: PeerId {
switch self {
case let .channelMember(channelMember):
return channelMember.peer.id
case let .legacyGroupMember(peer, _, _, _):
return peer.peerId
case let .account(peer):
return peer.peerId
}
}
var peer: Peer {
switch self {
case let .channelMember(channelMember):
return channelMember.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(channelMember):
return channelMember.presences[channelMember.peer.id] as? TelegramUserPresence
case let .legacyGroupMember(_, _, _, presence):
return presence
case .account:
return nil
}
}
var role: PeerInfoMemberRole {
switch self {
case let .channelMember(channelMember):
switch channelMember.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
}
}
enum
PeerInfoMembersDataState
1
2
case loading(isInitial: Bool)
case ready(canLoadMore: Bool)
struct
PeerInfoMembersState
1
2
3
var canAddMembers: Bool
var members: [PeerInfoMember]
var dataState: PeerInfoMembersDataState
enum
PeerChannelMemberContextKey
1
2
3
4
5
6
7
8
9
case recent
case recentSearch(String)
case mentions(threadId: MessageId?, query: String?)
case admins(String?)
case contacts(String?)
case bots(String?)
case restrictedAndBanned(String?)
case restricted(String?)
case banned(String?)
struct
PeerChannelMemberCategoryControl
1
fileprivate let key: PeerChannelMemberContextKey
class
PeerInfoMembersContextImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
private let queue: Queue
private let context: AccountContext
private let peerId: PeerId
// can add members
private var canAddMembers = false
// members
private var members: [PeerInfoMember] = []
// data state
private var dataState: PeerInfoMembersDataState = .loading(isInitial: true)
private var removingMemberIds: [PeerId: Disposable] = [:]
// state
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 {
// route to `peerChannelMemberCategoriesContextsManager` when `channel`
let (disposable, control) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { [weak self] state in
queue.async {
guard let strongSelf = self else {
return
}
let unsortedMembers = state.list.map(PeerInfoMember.channelMember)
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()
}
}))
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
// cached data when group
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))
}
}
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()
}
// push state
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)))
}
}
// load more
func loadMore() {
if case .ready(true) = self.dataState, let channelMembersControl = self.channelMembersControl {
self.context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: self.peerId, control: channelMembersControl)
}
}
// remove member
func removeMember(memberId: PeerId) {
if removingMemberIds[memberId] == nil {
let signal: Signal<Never, NoError>
if self.peerId.namespace == Namespaces.Peer.CloudChannel {
// ban read message right when channel
signal = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: self.context.engine, peerId: self.peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|> ignoreValues
} else {
// remove member when group
signal = self.context.engine.peers.removePeerMember(peerId: self.peerId, memberId: memberId)
|> ignoreValues
}
let completed: () -> Void = { [weak self] in
guard let strongSelf = self else {
return
}
// push state when complete
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()
}))
}
}
class
PeerInfoMembersContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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)
})
}
// wrapped load more
func loadMore() {
self.impl.with { impl in
impl.loadMore()
}
}
// wrapped remove member
func removeMember(memberId: PeerId) {
self.impl.with { impl in
impl.removeMember(memberId: memberId)
}
}