import Foundation import UIKit import Display import SwiftSignalKit import Postbox import TelegramCore import SyncCore import TelegramPresentationData import TelegramUIPreferences import ItemListUI import PresentationDataUtils import AccountContext import TemporaryCachedPeerDataManager import AlertUI import PresentationDataUtils import UndoUI import ItemListPeerItem import ItemListPeerActionItem private final class ChannelAdminsControllerArguments { let account: Account let openRecentActions: () -> Void let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let removeAdmin: (PeerId) -> Void let addAdmin: () -> Void let openAdmin: (ChannelParticipant) -> Void init(account: Account, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removeAdmin: @escaping (PeerId) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void) { self.account = account self.openRecentActions = openRecentActions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.removeAdmin = removeAdmin self.addAdmin = addAdmin self.openAdmin = openAdmin } } private enum ChannelAdminsSection: Int32 { case administration case admins } private enum ChannelAdminsEntryStableId: Hashable { case index(Int32) case peer(PeerId) var hashValue: Int { switch self { case let .index(index): return index.hashValue case let .peer(peerId): return peerId.hashValue } } static func ==(lhs: ChannelAdminsEntryStableId, rhs: ChannelAdminsEntryStableId) -> Bool { switch lhs { case let .index(index): if case .index(index) = rhs { return true } else { return false } case let .peer(peerId): if case .peer(peerId) = rhs { return true } else { return false } } } } private enum ChannelAdminsEntry: ItemListNodeEntry { case recentActions(PresentationTheme, String) case adminsHeader(PresentationTheme, String) case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Bool, Int32, RenderedChannelParticipant, ItemListPeerItemEditing, Bool, Bool) case addAdmin(PresentationTheme, String, Bool) case adminsInfo(PresentationTheme, String) var section: ItemListSectionId { switch self { case .recentActions: return ChannelAdminsSection.administration.rawValue case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo: return ChannelAdminsSection.admins.rawValue } } var stableId: ChannelAdminsEntryStableId { switch self { case .recentActions: return .index(0) case .adminsHeader: return .index(3) case .addAdmin: return .index(4) case .adminsInfo: return .index(5) case let .adminPeerItem(_, _, _, _, _, _, participant, _, _, _): return .peer(participant.peer.id) } } static func ==(lhs: ChannelAdminsEntry, rhs: ChannelAdminsEntry) -> Bool { switch lhs { case let .recentActions(lhsTheme, lhsText): if case let .recentActions(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .adminsHeader(lhsTheme, lhsText): if case let .adminsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsIsGroup, lhsIndex, lhsParticipant, lhsEditing, lhsEnabled, lhsHasAction): if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsIsGroup, rhsIndex, rhsParticipant, rhsEditing, rhsEnabled, rhsHasAction) = rhs { if lhsTheme !== rhsTheme { return false } if lhsStrings !== rhsStrings { return false } if lhsDateTimeFormat != rhsDateTimeFormat { return false } if lhsNameOrder != rhsNameOrder { return false } if lhsIsGroup != rhsIsGroup { return false } if lhsIndex != rhsIndex { return false } if lhsParticipant != rhsParticipant { return false } if lhsEditing != rhsEditing { return false } if lhsEnabled != rhsEnabled { return false } if lhsHasAction != rhsHasAction { return false } return true } else { return false } case let .adminsInfo(lhsTheme, lhsText): if case let .adminsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .addAdmin(lhsTheme, lhsText, lhsEditing): if case let .addAdmin(rhsTheme, rhsText, rhsEditing) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEditing == rhsEditing { return true } else { return false } } } static func <(lhs: ChannelAdminsEntry, rhs: ChannelAdminsEntry) -> Bool { switch lhs { case .recentActions: return true case .adminsHeader: switch rhs { case .recentActions: return false default: return true } case let .adminPeerItem(_, _, _, _, _, index, _, _, _, _): switch rhs { case .recentActions, .adminsHeader, .addAdmin: return false case let .adminPeerItem(_, _, _, _, _, rhsIndex, _, _, _, _): return index < rhsIndex default: return true } case .addAdmin: switch rhs { case .recentActions, .adminsHeader, .addAdmin: return false default: return true } case .adminsInfo: return false } } func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ChannelAdminsControllerArguments switch self { case let .recentActions(theme, text): return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openRecentActions() }) case let .adminsHeader(theme, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .adminPeerItem(theme, strings, dateTimeFormat, nameDisplayOrder, _, _, participant, editing, enabled, hasAction): let peerText: String var action: (() -> Void)? switch participant.participant { case .creator: peerText = strings.Channel_Management_LabelOwner case let .member(_, _, adminInfo, _, _): if let adminInfo = adminInfo { if let peer = participant.peers[adminInfo.promotedBy] { if peer.id == participant.peer.id { peerText = strings.Channel_Management_LabelAdministrator } else { peerText = strings.Channel_Management_PromotedBy(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)).0 } } else { peerText = "" } } else { peerText = "" } } if hasAction { action = { arguments.openAdmin(participant.participant) } } return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: participant.peer, presence: nil, text: .text(peerText), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.removeAdmin(peerId) }) case let .addAdmin(theme, text, editing): return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: editing, action: { arguments.addAdmin() }) case let .adminsInfo(theme, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) } } } private struct ChannelAdminsControllerState: Equatable { let editing: Bool let peerIdWithRevealedOptions: PeerId? let removingPeerId: PeerId? let removedPeerIds: Set let temporaryAdmins: [RenderedChannelParticipant] let searchingMembers: Bool init() { self.editing = false self.peerIdWithRevealedOptions = nil self.removingPeerId = nil self.removedPeerIds = Set() self.temporaryAdmins = [] self.searchingMembers = false } init(editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, removedPeerIds: Set, temporaryAdmins: [RenderedChannelParticipant], searchingMembers: Bool) { self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.removingPeerId = removingPeerId self.removedPeerIds = removedPeerIds self.temporaryAdmins = temporaryAdmins self.searchingMembers = searchingMembers } static func ==(lhs: ChannelAdminsControllerState, rhs: ChannelAdminsControllerState) -> Bool { if lhs.editing != rhs.editing { return false } if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions { return false } if lhs.removingPeerId != rhs.removingPeerId { return false } if lhs.removedPeerIds != rhs.removedPeerIds { return false } if lhs.temporaryAdmins != rhs.temporaryAdmins { return false } if lhs.searchingMembers != rhs.searchingMembers { return false } return true } func withUpdatedSearchingMembers(_ searchingMembers: Bool) -> ChannelAdminsControllerState { return ChannelAdminsControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: searchingMembers) } func withUpdatedEditing(_ editing: Bool) -> ChannelAdminsControllerState { return ChannelAdminsControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers) } func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelAdminsControllerState { return ChannelAdminsControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers) } func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelAdminsControllerState { return ChannelAdminsControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers) } func withUpdatedRemovedPeerIds(_ removedPeerIds: Set) -> ChannelAdminsControllerState { return ChannelAdminsControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers) } func withUpdatedTemporaryAdmins(_ temporaryAdmins: [RenderedChannelParticipant]) -> ChannelAdminsControllerState { return ChannelAdminsControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: temporaryAdmins, searchingMembers: self.searchingMembers) } } private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelAdminsEntry] { if participants == nil || participants?.count == nil { return [] } var entries: [ChannelAdminsEntry] = [] if let peer = view.peers[view.peerId] as? TelegramChannel { var isGroup = false if case .group = peer.info { isGroup = true entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog)) } else { entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog)) } if let participants = participants { entries.append(.adminsHeader(presentationData.theme, isGroup ? presentationData.strings.ChannelMembers_GroupAdminsTitle : presentationData.strings.ChannelMembers_ChannelAdminsTitle)) if peer.hasPermission(.addAdmins) { entries.append(.addAdmin(presentationData.theme, presentationData.strings.Channel_Management_AddModerator, state.editing)) } var combinedParticipants: [RenderedChannelParticipant] = participants var existingParticipantIds = Set() for participant in participants { existingParticipantIds.insert(participant.peer.id) } for participant in state.temporaryAdmins { if !existingParticipantIds.contains(participant.peer.id) { combinedParticipants.append(participant) } } var index: Int32 = 0 for participant in combinedParticipants.sorted(by: { lhs, rhs in let lhsInvitedAt: Int32 switch lhs.participant { case .creator: lhsInvitedAt = Int32.min case let .member(_, invitedAt, _, _, _): lhsInvitedAt = invitedAt } let rhsInvitedAt: Int32 switch rhs.participant { case .creator: rhsInvitedAt = Int32.min case let .member(_, invitedAt, _, _, _): rhsInvitedAt = invitedAt } return lhsInvitedAt < rhsInvitedAt }) { if !state.removedPeerIds.contains(participant.peer.id) { var canEdit = true var canOpen = true switch participant.participant { case .creator: canEdit = false canOpen = isGroup && peer.flags.contains(.isCreator) case let .member(id, _, adminInfo, _, _): if id == accountPeerId { canEdit = false } else if let adminInfo = adminInfo { if peer.flags.contains(.isCreator) || adminInfo.promotedBy == accountPeerId { canEdit = true } else { canEdit = false } } else { canEdit = false } } entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, isGroup, index, participant, ItemListPeerItemEditing(editable: canEdit, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id && existingParticipantIds.contains(participant.peer.id), canOpen)) index += 1 } } if peer.hasPermission(.addAdmins) { let info = isGroup ? presentationData.strings.Group_Management_AddModeratorHelp : presentationData.strings.Channel_Management_AddModeratorHelp entries.append(.adminsInfo(presentationData.theme, info)) } } } else if let peer = view.peers[view.peerId] as? TelegramGroup { let isGroup = true //entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog)) if let participants = participants { entries.append(.adminsHeader(presentationData.theme, presentationData.strings.ChannelMembers_GroupAdminsTitle)) if case .creator = peer.role { entries.append(.addAdmin(presentationData.theme, presentationData.strings.Channel_Management_AddModerator, state.editing)) } var combinedParticipants: [RenderedChannelParticipant] = participants var existingParticipantIds = Set() for participant in participants { existingParticipantIds.insert(participant.peer.id) } for participant in state.temporaryAdmins { if !existingParticipantIds.contains(participant.peer.id) { combinedParticipants.append(participant) } } var index: Int32 = 0 for participant in combinedParticipants.sorted(by: { lhs, rhs in let lhsInvitedAt: Int32 switch lhs.participant { case .creator: lhsInvitedAt = Int32.min case let .member(_, invitedAt, _, _, _): lhsInvitedAt = invitedAt } let rhsInvitedAt: Int32 switch rhs.participant { case .creator: rhsInvitedAt = Int32.min case let .member(_, invitedAt, _, _, _): rhsInvitedAt = invitedAt } return lhsInvitedAt < rhsInvitedAt }) { if !state.removedPeerIds.contains(participant.peer.id) { var editable = true var canEdit = true switch participant.participant { case .creator: editable = false if case .creator = peer.role { } else { canEdit = false } case let .member(id, _, adminInfo, _, _): if id == accountPeerId { editable = false } else if let adminInfo = adminInfo { if case .creator = peer.role { editable = true } else if adminInfo.promotedBy == accountPeerId { editable = true } else { editable = false } } else { editable = false } } entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, isGroup, index, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id && existingParticipantIds.contains(participant.peer.id), canEdit)) index += 1 } } if case .creator = peer.role { let info = presentationData.strings.Group_Management_AddModeratorHelp entries.append(.adminsInfo(presentationData.theme, info)) } } } return entries } public func channelAdminsController(context: AccountContext, peerId: PeerId, loadCompleted: @escaping () -> Void = {}) -> ViewController { let statePromise = ValuePromise(ChannelAdminsControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelAdminsControllerState()) let updateState: ((ChannelAdminsControllerState) -> ChannelAdminsControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? var dismissInputImpl: (() -> Void)? let actionsDisposable = DisposableSet() let removeAdminDisposable = MetaDisposable() actionsDisposable.add(removeAdminDisposable) let addAdminDisposable = MetaDisposable() actionsDisposable.add(addAdminDisposable) let upgradeDisposable = MetaDisposable() actionsDisposable.add(upgradeDisposable) let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil) let presentationDataSignal = context.sharedContext.presentationData var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)? let upgradedToSupergroup: (PeerId, @escaping () -> Void) -> Void = { upgradedPeerId, f in upgradedToSupergroupImpl?(upgradedPeerId, f) } let transferedOwnership: (PeerId) -> Void = { memberId in let presentationData = context.sharedContext.currentPresentationData.with { $0 } let _ = (context.account.postbox.transaction { transaction -> (channel: Peer?, user: Peer?) in return (channel: transaction.getPeer(peerId), user: transaction.getPeer(memberId)) } |> deliverOnMainQueue).start(next: { peer, user in guard let peer = peer, let user = user else { return } presentControllerImpl?(UndoOverlayController(presentationData: context.sharedContext.currentPresentationData.with { $0 }, content: .succeed(text: presentationData.strings.Channel_OwnershipTransfer_TransferCompleted(user.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).0), elevatedLayout: false, action: { _ in }), nil) }) } let peerView = Promise() peerView.set(context.account.viewTracker.peerView(peerId)) let arguments = ChannelAdminsControllerArguments(account: context.account, openRecentActions: { let _ = (context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { peer in if peer is TelegramGroup { } else { pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer)) } }) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in updateState { state in if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) { return state.withUpdatedPeerIdWithRevealedOptions(peerId) } else { return state } } }, removeAdmin: { adminId in updateState { return $0.withUpdatedRemovingPeerId(adminId) } if peerId.namespace == Namespaces.Peer.CloudGroup { removeAdminDisposable.set((removeGroupAdmin(account: context.account, peerId: peerId, adminId: adminId) |> deliverOnMainQueue).start(completed: { updateState { return $0.withUpdatedRemovingPeerId(nil) } })) } else { removeAdminDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(flags: []), rank: nil) |> deliverOnMainQueue).start(completed: { updateState { return $0.withUpdatedRemovingPeerId(nil) } })) } }, addAdmin: { let _ = (peerView.get() |> take(1) |> deliverOnMainQueue).start(next: { peerView in updateState { current in var dismissController: (() -> Void)? let controller = ChannelMembersSearchController(context: context, peerId: peerId, mode: .promote, filters: [], openPeer: { peer, participant in dismissController?() let presentationData = context.sharedContext.currentPresentationData.with { $0 } if peer.id == context.account.peerId { return } if let participant = participant { switch participant.participant { case .creator: return case let .member(_, _, _, banInfo, _): if let banInfo = banInfo { var canUnban = false if banInfo.restrictedBy != context.account.peerId { canUnban = true } if let channel = peerView.peers[peerId] as? TelegramChannel { if channel.hasPermission(.banMembers) { canUnban = true } } if !canUnban { presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_Members_AddAdminErrorBlacklisted, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) return } } } } pushControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) }) dismissController = { [weak controller] in controller?.dismiss() } pushControllerImpl?(controller) return current } }) }, openAdmin: { participant in pushControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) }) let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) if peerId.namespace == Namespaces.Peer.CloudChannel { var didReportLoadCompleted = false membersAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.admins(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) { membersState in if case .loading = membersState.loadingState, membersState.list.isEmpty { adminsPromise.set(.single(nil)) } else { adminsPromise.set(.single(membersState.list)) if !didReportLoadCompleted { didReportLoadCompleted = true loadCompleted() } } } } else { loadCompleted() let membersDisposable = (peerView.get() |> map { peerView -> [RenderedChannelParticipant]? in guard let cachedData = peerView.cachedData as? CachedGroupData, let participants = cachedData.participants else { return nil } var result: [RenderedChannelParticipant] = [] var creatorPeer: Peer? for participant in participants.participants { if let peer = peerView.peers[participant.peerId] { switch participant { case .creator: creatorPeer = peer default: break } } } guard let creator = creatorPeer else { return nil } for participant in participants.participants { if let peer = peerView.peers[participant.peerId] { switch participant { case .creator: result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer)) case .admin: var peers: [PeerId: Peer] = [:] peers[creator.id] = creator peers[peer.id] = peer result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: .groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers)) case .member: break } } } return result }).start(next: { members in adminsPromise.set(.single(members)) }) membersAndLoadMoreControl = (membersDisposable, nil) } let (membersDisposable, loadMoreControl) = membersAndLoadMoreControl actionsDisposable.add(membersDisposable) var previousPeers: [RenderedChannelParticipant]? let signal = combineLatest(queue: .mainQueue(), presentationDataSignal, statePromise.get(), peerView.get(), adminsPromise.get() |> deliverOnMainQueue) |> deliverOnMainQueue |> map { presentationData, state, view, admins -> (ItemListControllerState, (ItemListNodeState, Any)) in var rightNavigationButton: ItemListNavigationButton? var secondaryRightNavigationButton: ItemListNavigationButton? if let admins = admins, admins.count > 1 { if state.editing { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { updateState { state in return state.withUpdatedEditing(false) } }) } else if let peer = view.peers[peerId] as? TelegramChannel, peer.flags.contains(.isCreator) { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: { updateState { state in return state.withUpdatedEditing(true) } }) } if !state.editing && peerId.namespace == Namespaces.Peer.CloudChannel { if rightNavigationButton == nil { rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: { updateState { state in return state.withUpdatedSearchingMembers(true) } }) } else { secondaryRightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: { updateState { state in return state.withUpdatedSearchingMembers(true) } }) } } } let previous = previousPeers previousPeers = admins var isGroup = true if let peer = view.peers[peerId] as? TelegramChannel, case .broadcast = peer.info { isGroup = false } else if let _ = view.peers[peerId] as? TelegramGroup { isGroup = true } var searchItem: ItemListControllerSearch? if state.searchingMembers { searchItem = ChannelMembersSearchItem(context: context, peerId: peerId, searchContext: nil, searchMode: .searchAdmins, cancel: { updateState { state in return state.withUpdatedSearchingMembers(false) } }, openPeer: { _, participant in if let participant = participant?.participant, case .member = participant { pushControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in updateState { state in return state.withUpdatedSearchingMembers(false) } }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) } }, pushController: { c in pushControllerImpl?(c) }, dismissInput: { dismissInputImpl?() }) } var emptyStateItem: ItemListControllerEmptyStateItem? if admins == nil || admins?.count == 0 { emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, view: view, state: state, participants: admins), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() } let controller = ItemListController(context: context, state: signal) pushControllerImpl = { [weak controller] c in (controller?.navigationController as? NavigationController)?.pushViewController(c) } presentControllerImpl = { [weak controller] c, p in if let controller = controller { controller.present(c, in: .window(.root), with: p) controller.view.endEditing(true) } } dismissInputImpl = { [weak controller] in controller?.view.endEditing(true) } upgradedToSupergroupImpl = { [weak controller] upgradedPeerId, f in guard let controller = controller, let navigationController = controller.navigationController as? NavigationController else { return } rebuildControllerStackAfterSupergroupUpgrade(controller: controller, navigationController: navigationController) } controller.visibleBottomContentOffsetChanged = { offset in if case let .known(value) = offset, value < 40.0 { context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: peerId, control: loadMoreControl) } } return controller } func rebuildControllerStackAfterSupergroupUpgrade(controller: ViewController, navigationController: NavigationController) { var controllers = navigationController.viewControllers for i in 0 ..< controllers.count { if controllers[i] === controller { for j in 0 ..< i { if controllers[j] is ChatController { controllers.removeSubrange(j + 1 ... i - 1) break } } break } } navigationController.setViewControllers(controllers, animated: false) }