Group member search actions

This commit is contained in:
Peter 2018-12-11 12:18:38 +04:00
parent 2bea22c23b
commit 4881f49bf1
10 changed files with 465 additions and 231 deletions

View File

@ -473,7 +473,7 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
}
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)?
let actionsDisposable = DisposableSet()
@ -669,6 +669,8 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
}, present: { c, a in
presentControllerImpl?(c, a)
})
}

View File

@ -311,7 +311,7 @@ public func channelBlacklistController(account: Account, peerId: PeerId) -> View
statePromise.set(stateValue.modify { f($0) })
}
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let actionsDisposable = DisposableSet()
@ -477,6 +477,8 @@ public func channelBlacklistController(account: Account, peerId: PeerId) -> View
arguments.openPeerInfo(rendered.peer)
}
}
}, present: { c, a in
presentControllerImpl?(c, a)
})
}

View File

@ -379,9 +379,29 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
}
}
case let .recentSearch(query):
break
default:
break
if let updated = updated, isParticipantMember(updated.participant), updated.peer.indexName.matchesByTokens(query) {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
list[i] = updated
found = true
updatedList = true
break loop
}
}
if !found {
list.insert(updated, at: 0)
updatedList = true
}
} else if let previous = previous, isParticipantMember(previous) {
loop: for i in 0 ..< list.count {
if list[i].peer.id == previous.peerId {
list.remove(at: i)
updatedList = true
break loop
}
}
}
}
}
if updatedList {

View File

@ -310,7 +310,7 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo
statePromise.set(stateValue.modify { f($0) })
}
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let actionsDisposable = DisposableSet()
@ -444,6 +444,8 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo
pushControllerImpl?(infoController)
// arguments.pushController(infoController)
}
}, present: { c, a in
presentControllerImpl?(c, a)
})
}

View File

@ -35,7 +35,7 @@ private enum ChannelMembersSearchSection {
private enum ChannelMembersSearchContent: Equatable {
case peer(Peer)
case participant(participant: RenderedChannelParticipant, label: String?, revealActions: [ParticipantRevealAction], enabled: Bool)
case participant(participant: RenderedChannelParticipant, label: String?, revealActions: [ParticipantRevealAction], revealed: Bool, enabled: Bool)
static func ==(lhs: ChannelMembersSearchContent, rhs: ChannelMembersSearchContent) -> Bool {
switch lhs {
@ -45,8 +45,8 @@ private enum ChannelMembersSearchContent: Equatable {
} else {
return false
}
case let .participant(participant, label, revealActions, enabled):
if case .participant(participant, label, revealActions, enabled) = rhs {
case let .participant(participant, label, revealActions, revealed, enabled):
if case .participant(participant, label, revealActions, revealed, enabled) = rhs {
return true
} else {
return false
@ -58,12 +58,28 @@ private enum ChannelMembersSearchContent: Equatable {
switch self {
case let .peer(peer):
return peer.id
case let .participant(participant, _, _, _):
case let .participant(participant, _, _, _, _):
return participant.peer.id
}
}
}
private final class ChannelMembersSearchContainerInteraction {
let peerSelected: (Peer, RenderedChannelParticipant?) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let promotePeer: (RenderedChannelParticipant) -> Void
let restrictPeer: (RenderedChannelParticipant) -> Void
let removePeer: (PeerId) -> Void
init(peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, promotePeer: @escaping (RenderedChannelParticipant) -> Void, restrictPeer: @escaping (RenderedChannelParticipant) -> Void, removePeer: @escaping (PeerId) -> Void) {
self.peerSelected = peerSelected
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.promotePeer = promotePeer
self.restrictPeer = restrictPeer
self.removePeer = removePeer
}
}
private final class ChannelMembersSearchEntry: Comparable, Identifiable {
let index: Int
let content: ChannelMembersSearchContent
@ -87,31 +103,40 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable {
return lhs.index < rhs.index
}
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void) -> ListViewItem {
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchContainerInteraction) -> ListViewItem {
switch self.content {
case let .peer(peer):
return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in
peerSelected(peer, nil)
interaction.peerSelected(peer, nil)
})
case let .participant(participant, label, revealActions, enabled):
case let .participant(participant, label, revealActions, revealed, enabled):
let status: ContactsPeerItemStatus
if let label = label {
status = .custom(label)
} else {
status = .none
}
var options: [ItemListPeerItemRevealOption] = []
for action in revealActions {
options.append(ItemListPeerItemRevealOption(type: action.type, title: action.title, action: {
switch action.action {
case .promote:
//arguments.promotePeer(participant)
interaction.promotePeer(participant)
break
case .restrict:
//arguments.restrictPeer(participant)
interaction.restrictPeer(participant)
break
case .remove:
//arguments.removePeer(peer.id)
interaction.removePeer(participant.peer.id)
break
}
}))
}
return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: participant.peer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in
peerSelected(participant.peer, participant)
return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: participant.peer), status: status, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: revealed), options: options, index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in
interaction.peerSelected(participant.peer, participant)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId)
})
}
}
@ -123,16 +148,21 @@ struct ChannelMembersSearchContainerTransition {
let isSearching: Bool
}
private func channelMembersSearchContainerPreparedRecentTransition(from fromEntries: [ChannelMembersSearchEntry], to toEntries: [ChannelMembersSearchEntry], isSearching: Bool, account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void) -> ChannelMembersSearchContainerTransition {
private func channelMembersSearchContainerPreparedRecentTransition(from fromEntries: [ChannelMembersSearchEntry], to toEntries: [ChannelMembersSearchEntry], isSearching: Bool, account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchContainerInteraction) -> ChannelMembersSearchContainerTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, peerSelected: peerSelected), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, peerSelected: peerSelected), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction), directionHint: nil) }
return ChannelMembersSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching)
}
private struct ChannelMembersSearchContainerState: Equatable {
var revealedPeerId: PeerId?
var removingParticipantIds = Set<PeerId>()
}
final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNode {
private let account: Account
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
@ -150,9 +180,11 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private let removeMemberDisposable = MetaDisposable()
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)>
init(account: Account, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping(Bool)->Void) {
init(account: Account, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.account = account
self.openPeer = openPeer
self.mode = mode
@ -173,194 +205,323 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
self.addSubnode(self.dimNode)
self.addSubnode(self.listNode)
let themeAndStringsPromise = self.themeAndStringsPromise
let foundItems = searchQuery.get()
|> mapToSignal { query -> Signal<[ChannelMembersSearchEntry]?, NoError> in
updateActivity(true)
if let query = query, !query.isEmpty {
let foundGroupMembers: Signal<[RenderedChannelParticipant], NoError>
let foundMembers: Signal<[RenderedChannelParticipant], NoError>
switch mode {
case .searchMembers, .banAndPromoteActions:
foundGroupMembers = Signal { subscriber in
let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in
if case .ready = state.loadingState {
subscriber.putNext(state.list)
subscriber.putCompletion()
}
})
return disposable
}
|> runOn(Queue.mainQueue())
foundMembers = .single([])
case .inviteActions:
foundGroupMembers = .single([])
foundMembers = channelMembers(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, category: .recent(.search(query)))
|> map { $0 ?? [] }
case .searchAdmins:
foundGroupMembers = Signal { subscriber in
let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in
if case .ready = state.loadingState {
subscriber.putNext(state.list)
subscriber.putCompletion()
}
})
return disposable
} |> runOn(Queue.mainQueue())
foundMembers = .single([])
case .searchBanned:
foundGroupMembers = Signal { subscriber in
let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in
if case .ready = state.loadingState {
subscriber.putNext(state.list)
subscriber.putCompletion()
}
})
return disposable
} |> runOn(Queue.mainQueue())
foundMembers = .single([])
let statePromise = ValuePromise(ChannelMembersSearchContainerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelMembersSearchContainerState())
let updateState: ((ChannelMembersSearchContainerState) -> ChannelMembersSearchContainerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
let removeMemberDisposable = self.removeMemberDisposable
let interaction = ChannelMembersSearchContainerInteraction(peerSelected: { peer, participant in
openPeer(peer, participant)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
var state = state
if (peerId == nil && fromPeerId == state.revealedPeerId) || (peerId != nil && fromPeerId == nil) {
state.revealedPeerId = peerId
}
return state
}
}, promotePeer: { participant in
present(channelAdminController(account: account, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, restrictPeer: { participant in
present(channelBannedMemberController(account: account, peerId: peerId, memberId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, removePeer: { memberId in
let signal = account.postbox.loadedPeerWithId(memberId)
|> deliverOnMainQueue
|> mapToSignal { peer -> Signal<Bool, NoError> in
let result = ValuePromise<Bool>()
result.set(true)
return result.get()
}
|> mapToSignal { value -> Signal<Void, NoError> in
if value {
updateState { state in
var state = state
state.removingParticipantIds.insert(memberId)
return state
}
let foundContacts: Signal<([Peer], [PeerId: PeerPresence]), NoError>
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError>
switch mode {
case .inviteActions, .banAndPromoteActions:
foundContacts = account.postbox.searchContacts(query: query.lowercased())
foundRemotePeers = .single(([], [])) |> then(searchPeers(account: account, query: query)
|> delay(0.2, queue: Queue.concurrentDefaultQueue()))
case .searchMembers, .searchBanned, .searchAdmins:
foundContacts = .single(([], [:]))
foundRemotePeers = .single(([], []))
if peerId.namespace == Namespaces.Peer.CloudChannel {
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: account, peerId: peerId, memberId: memberId, bannedRights: TelegramChannelBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|> afterDisposed {
Queue.mainQueue().async {
updateState { state in
var state = state
state.removingParticipantIds.remove(memberId)
return state
}
}
}
}
return combineLatest(foundGroupMembers, foundMembers, foundContacts, foundRemotePeers, themeAndStringsPromise.get())
|> map { foundGroupMembers, foundMembers, foundContacts, foundRemotePeers, themeAndStrings -> [ChannelMembersSearchEntry]? in
var entries: [ChannelMembersSearchEntry] = []
var existingPeerIds = Set<PeerId>()
for filter in filters {
switch filter {
case let .exclude(ids):
existingPeerIds = existingPeerIds.union(ids)
case .disable:
break
}
}
switch mode {
case .inviteActions, .banAndPromoteActions:
existingPeerIds.insert(account.peerId)
case .searchMembers, .searchAdmins, .searchBanned:
break
}
var index = 0
for participant in foundGroupMembers {
if !existingPeerIds.contains(participant.peer.id) {
existingPeerIds.insert(participant.peer.id)
let section: ChannelMembersSearchSection
switch mode {
case .inviteActions, .banAndPromoteActions:
section = .members
case .searchMembers, .searchBanned, .searchAdmins:
section = .none
}
var label: String?
var enabled = true
if case .banAndPromoteActions = mode {
if case .creator = participant.participant {
label = themeAndStrings.1.Channel_Management_LabelCreator
enabled = false
}
}
switch mode {
case .searchAdmins:
switch participant.participant {
case .creator:
label = themeAndStrings.1.Channel_Management_LabelCreator
case let .member(_, _, adminInfo, _):
if let adminInfo = adminInfo {
if let peer = participant.peers[adminInfo.promotedBy] {
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
}
}
}
case .searchBanned:
switch participant.participant {
case let .member(_, _, _, banInfo):
if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] {
label = themeAndStrings.1.Channel_Management_RestrictedBy(peer.displayTitle).0
}
default:
break
}
default:
break
}
entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant: participant, label: label, revealActions: [], enabled: enabled), section: section))
index += 1
}
}
for participant in foundMembers {
if !existingPeerIds.contains(participant.peer.id) {
existingPeerIds.insert(participant.peer.id)
let section: ChannelMembersSearchSection
switch mode {
case .inviteActions, .banAndPromoteActions:
section = .members
case .searchMembers, .searchBanned, .searchAdmins:
section = .none
}
var label: String?
var enabled = true
if case .banAndPromoteActions = mode {
if case .creator = participant.participant {
label = themeAndStrings.1.Channel_Management_LabelCreator
enabled = false
}
}
entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant: participant, label: label, revealActions: [], enabled: enabled), section: section))
index += 1
}
}
for peer in foundContacts.0 {
if !existingPeerIds.contains(peer.id) {
existingPeerIds.insert(peer.id)
entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .contacts))
index += 1
}
}
for foundPeer in foundRemotePeers.0 {
let peer = foundPeer.peer
if !existingPeerIds.contains(peer.id) && peer is TelegramUser {
existingPeerIds.insert(peer.id)
entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global))
index += 1
}
}
for foundPeer in foundRemotePeers.1 {
let peer = foundPeer.peer
if !existingPeerIds.contains(peer.id) && peer is TelegramUser {
existingPeerIds.insert(peer.id)
entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global))
index += 1
}
}
return entries
return removePeerMember(account: account, peerId: peerId, memberId: memberId)
|> deliverOnMainQueue
|> afterDisposed {
updateState { state in
var state = state
state.removingParticipantIds.remove(memberId)
return state
}
}
} else {
return .single(nil)
return .complete()
}
}
removeMemberDisposable.set(signal.start())
})
let themeAndStringsPromise = self.themeAndStringsPromise
let foundItems = combineLatest(searchQuery.get(), account.postbox.multiplePeersView([peerId]) |> take(1))
|> mapToSignal { query, peerView -> Signal<[ChannelMembersSearchEntry]?, NoError> in
guard let channel = peerView.peers[peerId] as? TelegramChannel else {
return .single(nil)
}
updateActivity(true)
if let query = query, !query.isEmpty {
let foundGroupMembers: Signal<[RenderedChannelParticipant], NoError>
let foundMembers: Signal<[RenderedChannelParticipant], NoError>
switch mode {
case .searchMembers, .banAndPromoteActions:
foundGroupMembers = Signal { subscriber in
let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in
if case .ready = state.loadingState {
subscriber.putNext(state.list)
}
})
return disposable
}
|> runOn(Queue.mainQueue())
foundMembers = .single([])
case .inviteActions:
foundGroupMembers = .single([])
foundMembers = channelMembers(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, category: .recent(.search(query)))
|> map { $0 ?? [] }
case .searchAdmins:
foundGroupMembers = Signal { subscriber in
let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in
if case .ready = state.loadingState {
subscriber.putNext(state.list)
subscriber.putCompletion()
}
})
return disposable
} |> runOn(Queue.mainQueue())
foundMembers = .single([])
case .searchBanned:
foundGroupMembers = Signal { subscriber in
let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in
if case .ready = state.loadingState {
subscriber.putNext(state.list)
subscriber.putCompletion()
}
})
return disposable
}
|> runOn(Queue.mainQueue())
foundMembers = .single([])
}
let foundContacts: Signal<([Peer], [PeerId: PeerPresence]), NoError>
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError>
switch mode {
case .inviteActions, .banAndPromoteActions:
foundContacts = account.postbox.searchContacts(query: query.lowercased())
foundRemotePeers = .single(([], [])) |> then(searchPeers(account: account, query: query)
|> delay(0.2, queue: Queue.concurrentDefaultQueue()))
case .searchMembers, .searchBanned, .searchAdmins:
foundContacts = .single(([], [:]))
foundRemotePeers = .single(([], []))
}
return combineLatest(foundGroupMembers, foundMembers, foundContacts, foundRemotePeers, themeAndStringsPromise.get(), statePromise.get())
|> map { foundGroupMembers, foundMembers, foundContacts, foundRemotePeers, themeAndStrings, state -> [ChannelMembersSearchEntry]? in
var entries: [ChannelMembersSearchEntry] = []
var existingPeerIds = Set<PeerId>()
for filter in filters {
switch filter {
case let .exclude(ids):
existingPeerIds = existingPeerIds.union(ids)
case .disable:
break
}
}
switch mode {
case .inviteActions, .banAndPromoteActions:
existingPeerIds.insert(account.peerId)
case .searchMembers, .searchAdmins, .searchBanned:
break
}
var index = 0
for participant in foundGroupMembers {
if !existingPeerIds.contains(participant.peer.id) {
existingPeerIds.insert(participant.peer.id)
let section: ChannelMembersSearchSection
switch mode {
case .inviteActions, .banAndPromoteActions:
section = .members
case .searchMembers, .searchBanned, .searchAdmins:
section = .none
}
var canPromote: Bool
var canRestrict: Bool
switch participant.participant {
case .creator:
canPromote = false
canRestrict = false
case let .member(_, _, adminRights, bannedRights):
if channel.hasAdminRights([.canAddAdmins]) {
canPromote = true
} else {
canPromote = false
}
if channel.hasAdminRights([.canBanUsers]) {
canRestrict = true
} else {
canRestrict = false
}
if canPromote {
if let bannedRights = bannedRights {
if bannedRights.restrictedBy != account.peerId && !channel.flags.contains(.isCreator) {
canPromote = false
}
}
}
if canRestrict {
if let adminRights = adminRights {
if adminRights.promotedBy != account.peerId && !channel.flags.contains(.isCreator) {
canRestrict = false
}
}
}
}
var label: String?
var enabled = true
if case .banAndPromoteActions = mode {
if case .creator = participant.participant {
label = themeAndStrings.1.Channel_Management_LabelCreator
enabled = false
}
} else if case .searchMembers = mode {
switch participant.participant {
case .creator:
label = themeAndStrings.1.Channel_Management_LabelCreator
case let .member(member):
if member.adminInfo != nil {
label = themeAndStrings.1.Channel_Management_LabelEditor
}
}
}
if state.removingParticipantIds.contains(participant.peer.id) {
enabled = false
}
var peerActions: [ParticipantRevealAction] = []
if case .searchMembers = mode {
if canPromote {
peerActions.append(ParticipantRevealAction(type: .neutral, title: themeAndStrings.1.GroupInfo_ActionPromote, action: .promote))
}
if canRestrict {
peerActions.append(ParticipantRevealAction(type: .warning, title: themeAndStrings.1.GroupInfo_ActionRestrict, action: .restrict))
peerActions.append(ParticipantRevealAction(type: .destructive, title: themeAndStrings.1.Common_Delete, action: .remove))
}
}
switch mode {
case .searchAdmins:
switch participant.participant {
case .creator:
label = themeAndStrings.1.Channel_Management_LabelCreator
case let .member(_, _, adminInfo, _):
if let adminInfo = adminInfo {
if let peer = participant.peers[adminInfo.promotedBy] {
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
}
}
}
case .searchBanned:
switch participant.participant {
case let .member(_, _, _, banInfo):
if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] {
label = themeAndStrings.1.Channel_Management_RestrictedBy(peer.displayTitle).0
}
default:
break
}
default:
break
}
entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant: participant, label: label, revealActions: peerActions, revealed: state.revealedPeerId == participant.peer.id, enabled: enabled), section: section))
index += 1
}
}
for participant in foundMembers {
if !existingPeerIds.contains(participant.peer.id) {
existingPeerIds.insert(participant.peer.id)
let section: ChannelMembersSearchSection
switch mode {
case .inviteActions, .banAndPromoteActions:
section = .members
case .searchMembers, .searchBanned, .searchAdmins:
section = .none
}
var label: String?
var enabled = true
if case .banAndPromoteActions = mode {
if case .creator = participant.participant {
label = themeAndStrings.1.Channel_Management_LabelCreator
enabled = false
}
}
entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant: participant, label: label, revealActions: [], revealed: false, enabled: enabled), section: section))
index += 1
}
}
for peer in foundContacts.0 {
if !existingPeerIds.contains(peer.id) {
existingPeerIds.insert(peer.id)
entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .contacts))
index += 1
}
}
for foundPeer in foundRemotePeers.0 {
let peer = foundPeer.peer
if !existingPeerIds.contains(peer.id) && peer is TelegramUser {
existingPeerIds.insert(peer.id)
entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global))
index += 1
}
}
for foundPeer in foundRemotePeers.1 {
let peer = foundPeer.peer
if !existingPeerIds.contains(peer.id) && peer is TelegramUser {
existingPeerIds.insert(peer.id)
entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global))
index += 1
}
}
return entries
}
} else {
return .single(nil)
}
}
let previousSearchItems = Atomic<[ChannelMembersSearchEntry]?>(value: nil)
@ -371,24 +532,24 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
let previousEntries = previousSearchItems.swap(entries)
updateActivity(false)
let firstTime = previousEntries == nil
let transition = channelMembersSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, account: account, theme: themeAndStrings.0, strings: themeAndStrings.1, nameSortOrder: themeAndStrings.2, nameDisplayOrder: themeAndStrings.3, peerSelected: openPeer)
let transition = channelMembersSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, account: account, theme: themeAndStrings.0, strings: themeAndStrings.1, nameSortOrder: themeAndStrings.2, nameDisplayOrder: themeAndStrings.3, interaction: interaction)
strongSelf.enqueueTransition(transition, firstTime: firstTime)
}
}))
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
}
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
}
})
}
})
self.listNode.beganInteractiveDragging = { [weak self] in
self?.dismissInput?()
@ -398,6 +559,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
deinit {
self.searchDisposable.dispose()
self.presentationDataDisposable?.dispose()
self.removeMemberDisposable.dispose()
}
override func didLoad() {

View File

@ -70,6 +70,9 @@ final class ChannelMembersSearchController: ViewController {
self?.dismiss()
self?.openPeer(peer, participant)
}
self.controllerNode.present = { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
}
self.displayNodeDidLoad()
}

View File

@ -121,6 +121,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
var requestActivateSearch: (() -> Void)?
var requestDeactivateSearch: (() -> Void)?
var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)?
var present: ((ViewController, Any?) -> Void)?
var themeAndStrings: (PresentationTheme, PresentationStrings)
@ -292,6 +293,8 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
self?.requestOpenPeerFromSearch?(peer, participant)
}, updateActivity: { value in
}, present: { [weak self] c, a in
self?.present?(c, a)
}), cancel: { [weak self] in
if let requestDeactivateSearch = self?.requestDeactivateSearch {
requestDeactivateSearch()

View File

@ -115,6 +115,7 @@ class ContactsPeerItem: ListViewItem {
let enabled: Bool
let selection: ContactsPeerItemSelection
let editing: ContactsPeerItemEditing
let options: [ItemListPeerItemRevealOption]
let action: (ContactsPeerItemPeer) -> Void
let setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)?
let deletePeer: ((PeerId) -> Void)?
@ -125,7 +126,7 @@ class ContactsPeerItem: ListViewItem {
let header: ListViewItemHeader?
init(theme: PresentationTheme, strings: PresentationStrings, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, account: Account, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil) {
init(theme: PresentationTheme, strings: PresentationStrings, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, account: Account, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil) {
self.theme = theme
self.strings = strings
self.sortOrder = sortOrder
@ -138,6 +139,7 @@ class ContactsPeerItem: ListViewItem {
self.enabled = enabled
self.selection = selection
self.editing = editing
self.options = options
self.action = action
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.deletePeer = deletePeer
@ -562,6 +564,32 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 13.0), size: titleLayout.size)
}
let peerRevealOptions: [ItemListRevealOption]
if item.enabled {
var mappedOptions: [ItemListRevealOption] = []
var index: Int32 = 0
for option in item.options {
let color: UIColor
let textColor: UIColor
switch option.type {
case .neutral:
color = item.theme.list.itemDisclosureActions.constructive.fillColor
textColor = item.theme.list.itemDisclosureActions.constructive.foregroundColor
case .warning:
color = item.theme.list.itemDisclosureActions.warning.fillColor
textColor = item.theme.list.itemDisclosureActions.warning.foregroundColor
case .destructive:
color = item.theme.list.itemDisclosureActions.destructive.fillColor
textColor = item.theme.list.itemDisclosureActions.destructive.foregroundColor
}
mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor))
index += 1
}
peerRevealOptions = mappedOptions
} else {
peerRevealOptions = []
}
return (nodeLayout, { [weak self] in
if let strongSelf = self {
return (.complete(), { [weak strongSelf] animated, synchronousLoads in
@ -710,12 +738,12 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
if item.editing.editable {
strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated)
strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated)
} else {
strongSelf.setRevealOptions((left: [], right: []))
strongSelf.setRevealOptions((left: [], right: peerRevealOptions))
strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated)
}
}
})
@ -800,13 +828,17 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
if let item = self.item {
switch item.peer {
case let .peer(peer, chatPeer):
if let peer = chatPeer ?? peer {
item.deletePeer?(peer.id)
}
case .deviceContact:
break
if item.editing.editable {
switch item.peer {
case let .peer(peer, chatPeer):
if let peer = chatPeer ?? peer {
item.deletePeer?(peer.id)
}
case .deviceContact:
break
}
} else {
item.options[Int(option.key)].action()
}
}

View File

@ -1780,6 +1780,8 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
if let infoController = peerInfoController(account: account, peer: peer) {
arguments.pushController(infoController)
}
}, present: { c, a in
presentControllerImpl?(c, a)
})
}

View File

@ -10,15 +10,19 @@ final class ChannelMembersSearchItem: ItemListControllerSearch {
let peerId: PeerId
let cancel: () -> Void
let openPeer: (Peer, RenderedChannelParticipant?) -> Void
let present: (ViewController, Any?) -> Void
let searchMode: ChannelMembersSearchMode
private var updateActivity: ((Bool) -> Void)?
private var activity: ValuePromise<Bool> = ValuePromise(ignoreRepeated: false)
private let activityDisposable = MetaDisposable()
init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) {
init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.account = account
self.peerId = peerId
self.cancel = cancel
self.openPeer = openPeer
self.present = present
self.searchMode = searchMode
activityDisposable.set((activity.get() |> mapToSignal { value -> Signal<Bool, NoError> in
if value {
@ -32,7 +36,7 @@ final class ChannelMembersSearchItem: ItemListControllerSearch {
}
deinit {
activityDisposable.dispose()
self.activityDisposable.dispose()
}
func isEqual(to: ItemListControllerSearch) -> Bool {
@ -63,6 +67,8 @@ final class ChannelMembersSearchItem: ItemListControllerSearch {
func node(current: ItemListControllerSearchNode?) -> ItemListControllerSearchNode {
return ChannelMembersSearchItemNode(account: self.account, peerId: self.peerId, searchMode: self.searchMode, openPeer: self.openPeer, cancel: self.cancel, updateActivity: { [weak self] value in
self?.activity.set(value)
}, present: { [weak self] c, a in
self?.present(c, a)
})
}
}
@ -70,10 +76,10 @@ final class ChannelMembersSearchItem: ItemListControllerSearch {
private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode {
private let containerNode: ChannelMembersSearchContainerNode
init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool)->Void) {
init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.containerNode = ChannelMembersSearchContainerNode(account: account, peerId: peerId, mode: searchMode, filters: [], openPeer: { peer, participant in
openPeer(peer, participant)
}, updateActivity: updateActivity)
}, updateActivity: updateActivity, present: present)
self.containerNode.cancel = {
cancel()
}