Add peer categories in members lists

This commit is contained in:
Ilya Laktyushin 2022-01-23 03:22:19 +03:00
parent 47f3dde265
commit cc5de9372f
4 changed files with 228 additions and 71 deletions

View File

@ -7242,3 +7242,11 @@ Sorry for the inconvenience.";
"Conversation.CopyProtectionForwardingDisabledBot" = "Forwards from this bot are restricted"; "Conversation.CopyProtectionForwardingDisabledBot" = "Forwards from this bot are restricted";
"Conversation.CopyProtectionSavingDisabledBot" = "Saving from this bot is restricted"; "Conversation.CopyProtectionSavingDisabledBot" = "Saving from this bot is restricted";
"Channel.ChannelSubscribersHeader" = "CHANNEL SUBSCRIBERS";
"Channel.Members.Contacts" = "CONTACTS IN THIS CHANNEL";
"Channel.Members.Other" = "OTHERS SUBSCRIBERS";
"Group.Members.Contacts" = "CONTACTS IN THIS GROUP";
"Group.Members.Other" = "OTHERS MEMBERS";

View File

@ -26,6 +26,7 @@ public enum ChatListSearchItemHeaderType {
case activeVoiceChats case activeVoiceChats
case recentCalls case recentCalls
case orImportIntoAnExistingGroup case orImportIntoAnExistingGroup
case subscribers
fileprivate func title(strings: PresentationStrings) -> String { fileprivate func title(strings: PresentationStrings) -> String {
switch self { switch self {
@ -71,6 +72,8 @@ public enum ChatListSearchItemHeaderType {
return strings.CallList_RecentCallsHeader return strings.CallList_RecentCallsHeader
case .orImportIntoAnExistingGroup: case .orImportIntoAnExistingGroup:
return strings.ChatList_HeaderImportIntoAnExistingGroup return strings.ChatList_HeaderImportIntoAnExistingGroup
case .subscribers:
return strings.Channel_ChannelSubscribersHeader
} }
} }
@ -118,6 +121,8 @@ public enum ChatListSearchItemHeaderType {
return .recentCalls return .recentCalls
case .orImportIntoAnExistingGroup: case .orImportIntoAnExistingGroup:
return .orImportIntoAnExistingGroup return .orImportIntoAnExistingGroup
case .subscribers:
return .subscribers
} }
} }
} }
@ -148,6 +153,7 @@ private enum ChatListSearchItemHeaderId: Int32 {
case activeVoiceChats case activeVoiceChats
case recentCalls case recentCalls
case orImportIntoAnExistingGroup case orImportIntoAnExistingGroup
case subscribers
} }
public final class ChatListSearchItemHeader: ListViewItemHeader { public final class ChatListSearchItemHeader: ListViewItemHeader {

View File

@ -36,6 +36,7 @@ private final class ChannelMembersControllerArguments {
private enum ChannelMembersSection: Int32 { private enum ChannelMembersSection: Int32 {
case addMembers case addMembers
case contacts
case peers case peers
} }
@ -48,14 +49,20 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
case addMember(PresentationTheme, String) case addMember(PresentationTheme, String)
case addMemberInfo(PresentationTheme, String) case addMemberInfo(PresentationTheme, String)
case inviteLink(PresentationTheme, String) case inviteLink(PresentationTheme, String)
case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, RenderedChannelParticipant, ItemListPeerItemEditing, Bool) case contactsTitle(PresentationTheme, String)
case peersTitle(PresentationTheme, String)
case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, RenderedChannelParticipant, ItemListPeerItemEditing, Bool, Bool)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .addMember, .addMemberInfo, .inviteLink: case .addMember, .addMemberInfo, .inviteLink:
return ChannelMembersSection.addMembers.rawValue return ChannelMembersSection.addMembers.rawValue
case .peerItem: case .contactsTitle:
return ChannelMembersSection.contacts.rawValue
case .peersTitle:
return ChannelMembersSection.peers.rawValue return ChannelMembersSection.peers.rawValue
case let .peerItem(_, _, _, _, _, _, _, _, isContact):
return isContact ? ChannelMembersSection.contacts.rawValue : ChannelMembersSection.peers.rawValue
} }
} }
@ -65,9 +72,13 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
return .index(0) return .index(0)
case .addMemberInfo: case .addMemberInfo:
return .index(1) return .index(1)
case .inviteLink: case .inviteLink:
return .index(2) return .index(2)
case let .peerItem(_, _, _, _, _, participant, _, _): case .contactsTitle:
return .index(3)
case .peersTitle:
return .index(4)
case let .peerItem(_, _, _, _, _, participant, _, _, _):
return .peer(participant.peer.id) return .peer(participant.peer.id)
} }
} }
@ -86,14 +97,26 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .inviteLink(lhsTheme, lhsText): case let .inviteLink(lhsTheme, lhsText):
if case let .inviteLink(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .inviteLink(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
} else { } else {
return false return false
} }
case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsParticipant, lhsEditing, lhsEnabled): case let .contactsTitle(lhsTheme, lhsText):
if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsParticipant, rhsEditing, rhsEnabled) = rhs { if case let .contactsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .peersTitle(lhsTheme, lhsText):
if case let .peersTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsParticipant, lhsEditing, lhsEnabled, lhsIsContact):
if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsParticipant, rhsEditing, rhsEnabled, rhsIsContact) = rhs {
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
@ -118,6 +141,9 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
if lhsEnabled != rhsEnabled { if lhsEnabled != rhsEnabled {
return false return false
} }
if lhsIsContact != rhsIsContact {
return false
}
return true return true
} else { } else {
return false return false
@ -143,11 +169,30 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
default: default:
return true return true
} }
case .contactsTitle:
case let .peerItem(index, _, _, _, _, _, _, _):
switch rhs { switch rhs {
case let .peerItem(rhsIndex, _, _, _, _, _, _, _): case .addMember, .addMemberInfo, .inviteLink:
return index < rhsIndex return false
default:
return true
}
case .peersTitle:
switch rhs {
case .addMember, .addMemberInfo, .inviteLink, .contactsTitle:
return false
case let .peerItem(_, _, _, _, _, _, _, _, isContact):
return !isContact
default:
return true
}
case let .peerItem(lhsIndex, _, _, _, _, _, _, _, lhsIsContact):
switch rhs {
case .contactsTitle:
return false
case .peersTitle:
return lhsIsContact
case let .peerItem(rhsIndex, _, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex
case .addMember, .addMemberInfo, .inviteLink: case .addMember, .addMemberInfo, .inviteLink:
return false return false
} }
@ -167,7 +212,9 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
}) })
case let .addMemberInfo(_, text): case let .addMemberInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, participant, editing, enabled): case let .contactsTitle(_, text), let .peersTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, participant, editing, enabled, _):
let text: ItemListPeerItemText let text: ItemListPeerItemText
if let user = participant.peer as? TelegramUser, let _ = user.botInfo { if let user = participant.peer as? TelegramUser, let _ = user.botInfo {
text = .text(strings.Bot_GenericBotStatus, .secondary) text = .text(strings.Bot_GenericBotStatus, .secondary)
@ -238,7 +285,7 @@ private struct ChannelMembersControllerState: Equatable {
} }
} }
private func channelMembersControllerEntries(context: AccountContext, presentationData: PresentationData, view: PeerView, state: ChannelMembersControllerState, participants: [RenderedChannelParticipant]?, isGroup: Bool) -> [ChannelMembersEntry] { private func channelMembersControllerEntries(context: AccountContext, presentationData: PresentationData, view: PeerView, state: ChannelMembersControllerState, contacts: [RenderedChannelParticipant]?, participants: [RenderedChannelParticipant]?, isGroup: Bool) -> [ChannelMembersEntry] {
if participants == nil || participants?.count == nil { if participants == nil || participants?.count == nil {
return [] return []
} }
@ -251,6 +298,11 @@ private func channelMembersControllerEntries(context: AccountContext, presentati
canAddMember = peer.hasPermission(.inviteMembers) canAddMember = peer.hasPermission(.inviteMembers)
} }
var canEditMembers = false
if let peer = view.peers[view.peerId] as? TelegramChannel {
canEditMembers = peer.hasPermission(.banMembers)
}
if canAddMember { if canAddMember {
entries.append(.addMember(presentationData.theme, isGroup ? presentationData.strings.Group_Members_AddMembers : presentationData.strings.Channel_Members_AddMembers)) entries.append(.addMember(presentationData.theme, isGroup ? presentationData.strings.Group_Members_AddMembers : presentationData.strings.Channel_Members_AddMembers))
if let peer = view.peers[view.peerId] as? TelegramChannel, peer.addressName == nil { if let peer = view.peers[view.peerId] as? TelegramChannel, peer.addressName == nil {
@ -267,14 +319,44 @@ private func channelMembersControllerEntries(context: AccountContext, presentati
var index: Int32 = 0 var index: Int32 = 0
let sortedParticipants = participants var existingPeerIds = Set<PeerId>()
for participant in sortedParticipants {
var editable = true var addedContactsHeader = false
var canEditMembers = false if let contacts = contacts, !contacts.isEmpty {
if let peer = view.peers[view.peerId] as? TelegramChannel { addedContactsHeader = true
canEditMembers = peer.hasPermission(.banMembers)
entries.append(.contactsTitle(presentationData.theme, isGroup ? presentationData.strings.Group_Members_Contacts : presentationData.strings.Channel_Members_Contacts))
for participant in contacts {
var editable = true
if participant.peer.id == context.account.peerId {
editable = false
} else {
switch participant.participant {
case .creator:
editable = false
case .member:
editable = canEditMembers
}
}
entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id, true))
existingPeerIds.insert(participant.peer.id)
index += 1
}
}
var addedOtherHeader = false
for participant in participants {
if existingPeerIds.contains(participant.peer.id) {
continue
} }
if addedContactsHeader && !addedOtherHeader {
addedOtherHeader = true
entries.append(.peersTitle(presentationData.theme, isGroup ? presentationData.strings.Group_Members_Other : presentationData.strings.Channel_Members_Other))
}
var editable = true
if participant.peer.id == context.account.peerId { if participant.peer.id == context.account.peerId {
editable = false editable = false
} else { } else {
@ -285,7 +367,7 @@ private func channelMembersControllerEntries(context: AccountContext, presentati
editable = canEditMembers editable = canEditMembers
} }
} }
entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id)) entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id, false))
index += 1 index += 1
} }
} }
@ -315,6 +397,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
actionsDisposable.add(removePeerDisposable) actionsDisposable.add(removePeerDisposable)
let peersPromise = Promise<[RenderedChannelParticipant]?>(nil) let peersPromise = Promise<[RenderedChannelParticipant]?>(nil)
let contactsPromise = Promise<[RenderedChannelParticipant]?>(nil)
let arguments = ChannelMembersControllerArguments(context: context, addMember: { let arguments = ChannelMembersControllerArguments(context: context, addMember: {
actionsDisposable.add((peersPromise.get() actionsDisposable.add((peersPromise.get()
@ -437,17 +520,22 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
let peerView = context.account.viewTracker.peerView(peerId) let peerView = context.account.viewTracker.peerView(peerId)
let (contactsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: { state in
contactsPromise.set(.single(state.list))
})
let (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in let (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
peersPromise.set(.single(state.list)) peersPromise.set(.single(state.list))
}) })
actionsDisposable.add(disposable) actionsDisposable.add(disposable)
actionsDisposable.add(contactsDisposable)
var previousPeers: [RenderedChannelParticipant]? var currentContacts: [RenderedChannelParticipant]?
var currentPeers: [RenderedChannelParticipant]?
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
let signal = combineLatest(queue: .mainQueue(), presentationData, statePromise.get(), peerView, peersPromise.get()) let signal = combineLatest(queue: .mainQueue(), presentationData, statePromise.get(), peerView, contactsPromise.get(), peersPromise.get())
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, peers -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, view, contacts, peers -> (ItemListControllerState, (ItemListNodeState, Any)) in
var isGroup = true var isGroup = true
if let peer = peerViewMainPeer(view) as? TelegramChannel, case .broadcast = peer.info { if let peer = peerViewMainPeer(view) as? TelegramChannel, case .broadcast = peer.info {
isGroup = false isGroup = false
@ -455,7 +543,14 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
var rightNavigationButton: ItemListNavigationButton? var rightNavigationButton: ItemListNavigationButton?
var secondaryRightNavigationButton: ItemListNavigationButton? var secondaryRightNavigationButton: ItemListNavigationButton?
if let peers = peers, !peers.isEmpty {
var isEmpty = true
if let contacts = contacts, !contacts.isEmpty {
isEmpty = false
} else if let peers = peers, !peers.isEmpty {
isEmpty = false
}
if !isEmpty {
if state.editing { if state.editing {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
updateState { state in updateState { state in
@ -497,15 +592,26 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
} }
var emptyStateItem: ItemListControllerEmptyStateItem? var emptyStateItem: ItemListControllerEmptyStateItem?
if peers == nil || peers?.count == 0 { if isEmpty {
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
} }
let previous = previousPeers let previousContacts = currentContacts
previousPeers = peers currentContacts = contacts
let previousPeers = currentPeers
currentPeers = peers
var animateChanges = false
if let previousContacts = previousContacts, let contacts = contacts, previousContacts.count >= contacts.count {
animateChanges = true
}
if let previousPeers = previousPeers, let peers = peers, previousPeers.count >= peers.count {
animateChanges = true
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.Group_Members_Title : presentationData.strings.Channel_Subscribers_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.Group_Members_Title : presentationData.strings.Channel_Subscribers_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelMembersControllerEntries(context: context, presentationData: presentationData, view: view, state: state, participants: peers, isGroup: isGroup), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && peers != nil && previous!.count >= peers!.count) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelMembersControllerEntries(context: context, presentationData: presentationData, view: view, state: state, contacts: contacts, participants: peers, isGroup: isGroup), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: animateChanges)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }

View File

@ -38,14 +38,14 @@ private enum ChannelMembersSearchEntryId: Hashable {
private enum ChannelMembersSearchEntry: Comparable, Identifiable { private enum ChannelMembersSearchEntry: Comparable, Identifiable {
case copyInviteLink case copyInviteLink
case peer(Int, RenderedChannelParticipant, ContactsPeerItemEditing, String?, Bool) case peer(Int, RenderedChannelParticipant, ContactsPeerItemEditing, String?, Bool, Bool, Bool)
case contact(Int, Peer, TelegramUserPresence?) case contact(Int, Peer, TelegramUserPresence?)
var stableId: ChannelMembersSearchEntryId { var stableId: ChannelMembersSearchEntryId {
switch self { switch self {
case .copyInviteLink: case .copyInviteLink:
return .copyInviteLink return .copyInviteLink
case let .peer(_, participant, _, _, _): case let .peer(_, participant, _, _, _, _, _):
return .peer(participant.peer.id) return .peer(participant.peer.id)
case let .contact(_, peer, _): case let .contact(_, peer, _):
return .contact(peer.id) return .contact(peer.id)
@ -60,8 +60,8 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled): case let .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled, lhsIsChannel, lhsIsContact):
if case .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled) = rhs { if case .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled, lhsIsChannel, lhsIsContact) = rhs {
return true return true
} else { } else {
return false return false
@ -92,10 +92,10 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
} else { } else {
return true return true
} }
case let .peer(lhsIndex, _, _, _, _): case let .peer(lhsIndex, _, _, _, _, _, _):
if case .copyInviteLink = rhs { if case .copyInviteLink = rhs {
return false return false
} else if case let .peer(rhsIndex, _, _, _, _) = rhs { } else if case let .peer(rhsIndex, _, _, _, _, _, _) = rhs {
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
} else if case .contact = rhs { } else if case .contact = rhs {
return true return true
@ -127,7 +127,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.VoiceChat_CopyInviteLink, icon: icon, clearHighlightAutomatically: true, header: nil, action: { return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.VoiceChat_CopyInviteLink, icon: icon, clearHighlightAutomatically: true, header: nil, action: {
interaction.copyInviteLink() interaction.copyInviteLink()
}) })
case let .peer(_, participant, editing, label, enabled): case let .peer(_, participant, editing, label, enabled, isChannel, isContact):
let status: ContactsPeerItemStatus let status: ContactsPeerItemStatus
if let label = label { if let label = label {
status = .custom(string: label, multiline: false) status = .custom(string: label, multiline: false)
@ -138,7 +138,14 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
status = .none status = .none
} }
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(participant.peer), chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: .groupMembers, theme: presentationData.theme, strings: presentationData.strings), action: { _ in let headerType: ChatListSearchItemHeaderType
if isContact {
headerType = .contacts
} else {
headerType = isChannel ? .subscribers : .groupMembers
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(participant.peer), chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings), action: { _ in
interaction.openPeer(participant.peer, participant) interaction.openPeer(participant.peer, participant)
}) })
case let .contact(_, peer, presence): case let .contact(_, peer, presence):
@ -239,6 +246,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
let previousEntries = Atomic<[ChannelMembersSearchEntry]?>(value: nil) let previousEntries = Atomic<[ChannelMembersSearchEntry]?>(value: nil)
let disposableAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) let disposableAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?)
let contactsDisposableAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?)?
let additionalDisposable = MetaDisposable() let additionalDisposable = MetaDisposable()
if peerId.namespace == Namespaces.Peer.CloudGroup { if peerId.namespace == Namespaces.Peer.CloudGroup {
@ -398,7 +406,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers, presences: peerView.peerPresences) renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers, presences: peerView.peerPresences)
} }
entries.append(.peer(index, renderedParticipant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled)) entries.append(.peer(index, renderedParticipant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled, false, false))
index += 1 index += 1
} }
@ -420,6 +428,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
strongSelf.enqueueTransition(preparedTransition(from: previous, to: entries, context: context, presentationData: strongSelf.presentationData, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, interaction: interaction)) strongSelf.enqueueTransition(preparedTransition(from: previous, to: entries, context: context, presentationData: strongSelf.presentationData, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, interaction: interaction))
}) })
disposableAndLoadMoreControl = (disposable, nil) disposableAndLoadMoreControl = (disposable, nil)
contactsDisposableAndLoadMoreControl = nil
} else { } else {
let membersState = Promise<ChannelMemberListState>() let membersState = Promise<ChannelMemberListState>()
@ -427,19 +436,26 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
membersState.set(.single(state)) membersState.set(.single(state))
}) })
let contactsState = Promise<ChannelMemberListState>()
contactsDisposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: { state in
contactsState.set(.single(state))
})
additionalDisposable.set((combineLatest(queue: .mainQueue(), additionalDisposable.set((combineLatest(queue: .mainQueue(),
membersState.get(), membersState.get(),
contactsState.get(),
context.account.postbox.peerView(id: peerId), context.account.postbox.peerView(id: peerId),
context.engine.data.subscribe( context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Contacts.List(includePresences: true) TelegramEngine.EngineData.Item.Contacts.List(includePresences: true)
) )
).start(next: { [weak self] state, peerView, contactsView in ).start(next: { [weak self] state, contactsState, peerView, contactsView in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
var entries: [ChannelMembersSearchEntry] = [] var entries: [ChannelMembersSearchEntry] = []
var canInviteByLink = false var canInviteByLink = false
var isChannel = false
if let peer = peerViewMainPeer(peerView) { if let peer = peerViewMainPeer(peerView) {
if !(peer.addressName?.isEmpty ?? true) { if !(peer.addressName?.isEmpty ?? true) {
canInviteByLink = true canInviteByLink = true
@ -447,6 +463,9 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
if peer.flags.contains(.isCreator) || (peer.adminRights?.rights.contains(.canInviteUsers) == true) { if peer.flags.contains(.isCreator) || (peer.adminRights?.rights.contains(.canInviteUsers) == true) {
canInviteByLink = true canInviteByLink = true
} }
if case .broadcast = peer.info {
isChannel = true
}
} else if let peer = peer as? TelegramGroup { } else if let peer = peer as? TelegramGroup {
if case .creator = peer.role { if case .creator = peer.role {
canInviteByLink = true canInviteByLink = true
@ -456,6 +475,8 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
} }
} }
var index = 0
var existingPeersIds = Set<PeerId>()
if case .inviteToCall = mode, canInviteByLink, !filters.contains(where: { filter in if case .inviteToCall = mode, canInviteByLink, !filters.contains(where: { filter in
if case .excludeNonMembers = filter { if case .excludeNonMembers = filter {
return true return true
@ -464,18 +485,53 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
} }
}) { }) {
entries.append(.copyInviteLink) entries.append(.copyInviteLink)
} else {
contactsLoop: for participant in contactsState.list {
if participant.peer.isDeleted {
continue contactsLoop
}
var label: String?
var enabled = true
for filter in filters {
switch filter {
case let .exclude(ids):
if ids.contains(participant.peer.id) {
continue contactsLoop
}
case let .disable(ids):
if ids.contains(participant.peer.id) {
enabled = false
}
case .excludeNonMembers:
break
case .excludeBots:
if let user = participant.peer as? TelegramUser, user.botInfo != nil {
continue contactsLoop
}
}
}
if case .promote = mode, case .creator = participant.participant {
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
enabled = false
}
entries.append(.peer(index, participant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled, isChannel, true))
index += 1
existingPeersIds.insert(participant.peer.id)
}
} }
var index = 0
participantsLoop: for participant in state.list { participantsLoop: for participant in state.list {
if participant.peer.isDeleted { if participant.peer.isDeleted || existingPeersIds.contains(participant.peer.id) {
continue participantsLoop continue participantsLoop
} }
var label: String? var label: String?
var enabled = true var enabled = true
switch mode { switch mode {
case .ban: case .ban, .promote:
if participant.peer.id == context.account.peerId { if participant.peer.id == context.account.peerId {
continue participantsLoop continue participantsLoop
} }
@ -497,29 +553,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
} }
} }
} }
case .promote: if case .promote = mode, case .creator = participant.participant {
if participant.peer.id == context.account.peerId {
continue
}
for filter in filters {
switch filter {
case let .exclude(ids):
if ids.contains(participant.peer.id) {
continue participantsLoop
}
case let .disable(ids):
if ids.contains(participant.peer.id) {
enabled = false
}
case .excludeNonMembers:
break
case .excludeBots:
if let user = participant.peer as? TelegramUser, user.botInfo != nil {
continue participantsLoop
}
}
}
if case .creator = participant.participant {
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
enabled = false enabled = false
} }
@ -549,7 +583,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
} }
} }
} }
entries.append(.peer(index, participant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled)) entries.append(.peer(index, participant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled, isChannel, false))
index += 1 index += 1
} }
@ -575,6 +609,9 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
let combinedDisposable = DisposableSet() let combinedDisposable = DisposableSet()
combinedDisposable.add(disposableAndLoadMoreControl.0) combinedDisposable.add(disposableAndLoadMoreControl.0)
combinedDisposable.add(additionalDisposable) combinedDisposable.add(additionalDisposable)
if let disposable = contactsDisposableAndLoadMoreControl?.0 {
combinedDisposable.add(disposable)
}
self.disposable = combinedDisposable self.disposable = combinedDisposable
self.listControl = disposableAndLoadMoreControl.1 self.listControl = disposableAndLoadMoreControl.1