From 06d4ea29758052ce8bfb14a6d36149af9ce8022b Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 10 Jul 2017 19:13:40 +0300 Subject: [PATCH] Use ParticipantsBoxController for members list. Add search in channel/supergroup members inside PeerListBox. Also MembersBox is not used anymore. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/abstract_box.cpp | 22 ++- Telegram/SourceFiles/boxes/abstract_box.h | 6 +- .../profile/profile_block_channel_members.cpp | 2 +- .../profile/profile_block_settings.cpp | 12 ++ .../profile/profile_block_settings.h | 5 +- .../profile/profile_channel_controllers.cpp | 173 ++++++++++++++---- .../profile/profile_channel_controllers.h | 10 +- 8 files changed, 187 insertions(+), 44 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 95e6f76d8..822866e1c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -557,6 +557,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_profile_invite_link_section" = "Invite link"; "lng_profile_create_public_link" = "Create public link"; "lng_profile_edit_public_link" = "Edit public link"; +"lng_profile_search_members" = "Search members"; "lng_profile_manage_admins" = "Manage administrators"; "lng_profile_manage_blocklist" = "Manage banned users"; "lng_profile_manage_restrictedlist" = "Manage restricted users"; diff --git a/Telegram/SourceFiles/boxes/abstract_box.cpp b/Telegram/SourceFiles/boxes/abstract_box.cpp index d37f9fda4..293c505ed 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.cpp +++ b/Telegram/SourceFiles/boxes/abstract_box.cpp @@ -58,9 +58,10 @@ void BoxContent::setInner(object_ptr inner, const style::ScrollArea &st _topShadow.create(this, object_ptr(this)); _bottomShadow.create(this, object_ptr(this)); } - updateScrollAreaGeometry(); - connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(_scroll, SIGNAL(innerResized()), this, SLOT(onInnerResize())); + if (!_preparing) { + // We didn't set dimensions yet, this will be called from finishPrepare(); + finishScrollCreate(); + } } else { getDelegate()->setLayerType(false); _scroll.destroyDelayed(); @@ -69,6 +70,21 @@ void BoxContent::setInner(object_ptr inner, const style::ScrollArea &st } } +void BoxContent::finishPrepare() { + _preparing = false; + if (_scroll) { + finishScrollCreate(); + } + setInnerFocus(); +} + +void BoxContent::finishScrollCreate() { + Expects(_scroll != nullptr); + updateScrollAreaGeometry(); + connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(_scroll, SIGNAL(innerResized()), this, SLOT(onInnerResize())); +} + void BoxContent::onScrollToY(int top, int bottom) { if (_scroll) { _scroll->scrollToY(top, bottom); diff --git a/Telegram/SourceFiles/boxes/abstract_box.h b/Telegram/SourceFiles/boxes/abstract_box.h index 0bba4d45b..e70f89f14 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.h +++ b/Telegram/SourceFiles/boxes/abstract_box.h @@ -111,8 +111,9 @@ public: void setDelegate(BoxContentDelegate *newDelegate) { _delegate = newDelegate; + _preparing = true; prepare(); - setInnerFocus(); + finishPrepare(); } public slots: @@ -177,6 +178,8 @@ private slots: void onDraggingScrollTimer(); private: + void finishPrepare(); + void finishScrollCreate(); void setInner(object_ptr inner); void setInner(object_ptr inner, const style::ScrollArea &st); void updateScrollAreaGeometry(); @@ -190,6 +193,7 @@ private: } BoxContentDelegate *_delegate = nullptr; + bool _preparing = false; bool _noContentMargin = false; int _innerTopSkip = 0; object_ptr _scroll = { nullptr }; diff --git a/Telegram/SourceFiles/profile/profile_block_channel_members.cpp b/Telegram/SourceFiles/profile/profile_block_channel_members.cpp index 313914520..367050f22 100644 --- a/Telegram/SourceFiles/profile/profile_block_channel_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_channel_members.cpp @@ -144,7 +144,7 @@ int ChannelMembersWidget::resizeGetHeight(int newWidth) { void ChannelMembersWidget::onMembers() { if (auto channel = peer()->asChannel()) { - Ui::show(Box(channel, MembersFilter::Recent)); + ParticipantsBoxController::Start(channel, ParticipantsBoxController::Role::Members); } } diff --git a/Telegram/SourceFiles/profile/profile_block_settings.cpp b/Telegram/SourceFiles/profile/profile_block_settings.cpp index 78c74e123..4d7e841fe 100644 --- a/Telegram/SourceFiles/profile/profile_block_settings.cpp +++ b/Telegram/SourceFiles/profile/profile_block_settings.cpp @@ -96,6 +96,7 @@ int SettingsWidget::resizeGetHeight(int newWidth) { button->moveToLeft(left, newHeight); newHeight += button->height(); }; + moveLink(_searchMembers); moveLink(_manageAdmins); moveLink(_recentActions); moveLink(_manageBannedUsers); @@ -109,6 +110,11 @@ int SettingsWidget::resizeGetHeight(int newWidth) { void SettingsWidget::refreshButtons() { refreshEnableNotifications(); refreshManageAdminsButton(); + if (auto megagroup = peer()->asMegagroup()) { + _searchMembers.create(this, lang(lng_profile_search_members), st::defaultLeftOutlineButton); + _searchMembers->show(); + connect(_searchMembers, SIGNAL(clicked()), this, SLOT(onSearchMembers())); + } refreshManageBannedUsersButton(); refreshInviteLinkButton(); } @@ -208,6 +214,12 @@ void SettingsWidget::onNotificationsChange() { App::main()->updateNotifySetting(peer(), _enableNotifications->checked() ? NotifySettingSetNotify : NotifySettingSetMuted); } +void SettingsWidget::onSearchMembers() { + if (auto channel = peer()->asChannel()) { + ParticipantsBoxController::Start(channel, ParticipantsBoxController::Role::Members); + } +} + void SettingsWidget::onManageAdmins() { if (auto chat = peer()->asChat()) { Ui::show(Box(chat, MembersFilter::Admins)); diff --git a/Telegram/SourceFiles/profile/profile_block_settings.h b/Telegram/SourceFiles/profile/profile_block_settings.h index bdbd221e3..6bb399957 100644 --- a/Telegram/SourceFiles/profile/profile_block_settings.h +++ b/Telegram/SourceFiles/profile/profile_block_settings.h @@ -45,6 +45,7 @@ protected: private slots: void onNotificationsChange(); + void onSearchMembers(); void onManageAdmins(); void onRecentActions(); void onManageBannedUsers(); @@ -62,9 +63,7 @@ private: void refreshInviteLinkButton(); object_ptr _enableNotifications; - - // In groups: creator of non-deactivated groups can see this link. - // In channels: creator of supergroup can see this link. + object_ptr _searchMembers = { nullptr }; object_ptr _manageAdmins = { nullptr }; object_ptr _recentActions = { nullptr }; object_ptr _manageBannedUsers = { nullptr }; diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp index 3672ae064..b2b901b88 100644 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp +++ b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp @@ -37,7 +37,7 @@ constexpr auto kParticipantsPerPage = 200; } // namespace -ParticipantsBoxController::ParticipantsBoxController(gsl::not_null channel, Role role) : PeerListController((role == Role::Admins) ? nullptr : std::make_unique(channel, role, &_additional)) +ParticipantsBoxController::ParticipantsBoxController(gsl::not_null channel, Role role) : PeerListController((role == Role::Admins) ? nullptr : std::make_unique(channel, role, &_additional)) , _channel(channel) , _role(role) { if (_channel->mgInfo) { @@ -51,6 +51,7 @@ void ParticipantsBoxController::Start(gsl::not_null channel, Role box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); auto canAddNewItem = [role, channel] { switch (role) { + case Role::Members: return false; case Role::Admins: return channel->canAddAdmins(); case Role::Restricted: case Role::Kicked: return channel->canBanMembers(); @@ -104,7 +105,7 @@ std::unique_ptr ParticipantsBoxController::createSearchRow(gsl::not template void ParticipantsBoxController::HandleParticipant(const MTPChannelParticipant &participant, Role role, gsl::not_null additional, Callback callback) { - if (role == Role::Admins && participant.type() == mtpc_channelParticipantAdmin) { + if ((role == Role::Members || role == Role::Admins) && participant.type() == mtpc_channelParticipantAdmin) { auto &admin = participant.c_channelParticipantAdmin(); if (auto user = App::userLoaded(admin.vuser_id.v)) { additional->adminRights[user] = admin.vadmin_rights; @@ -125,18 +126,28 @@ void ParticipantsBoxController::HandleParticipant(const MTPChannelParticipant &p } callback(user); } - } else if (role == Role::Admins && participant.type() == mtpc_channelParticipantCreator) { + } else if ((role == Role::Members || role == Role::Admins) && participant.type() == mtpc_channelParticipantCreator) { auto &creator = participant.c_channelParticipantCreator(); if (auto user = App::userLoaded(creator.vuser_id.v)) { additional->creator = user; callback(user); } - } else if ((role == Role::Restricted || role == Role::Kicked) && participant.type() == mtpc_channelParticipantBanned) { + } else if ((role == Role::Members || role == Role::Restricted || role == Role::Kicked) && participant.type() == mtpc_channelParticipantBanned) { auto &banned = participant.c_channelParticipantBanned(); if (auto user = App::userLoaded(banned.vuser_id.v)) { additional->restrictedRights[user] = banned.vbanned_rights; callback(user); } + } else if (role == Role::Members && participant.type() == mtpc_channelParticipant) { + auto &member = participant.c_channelParticipant(); + if (auto user = App::userLoaded(member.vuser_id.v)) { + callback(user); + } + } else if (role == Role::Members && participant.type() == mtpc_channelParticipantSelf) { + auto &member = participant.c_channelParticipantSelf(); + if (auto user = App::userLoaded(member.vuser_id.v)) { + callback(user); + } } else { LOG(("API Error: Bad participant type got while requesting for participants: %1").arg(participant.type())); } @@ -148,13 +159,19 @@ void ParticipantsBoxController::prepare() { delegate()->peerListSetTitle(langFactory(lng_channel_admins)); } else { delegate()->peerListSetSearchMode(PeerListSearchMode::Complex); - delegate()->peerListSetTitle(langFactory((_role == Role::Restricted) ? lng_restricted_list_title : lng_banned_list_title)); + if (_role == Role::Members) { + delegate()->peerListSetTitle(langFactory(lng_profile_participants_section)); + } else if (_role == Role::Restricted) { + delegate()->peerListSetTitle(langFactory(lng_restricted_list_title)); + } else { + delegate()->peerListSetTitle(langFactory(lng_banned_list_title)); + } } setDescriptionText(lang(lng_contacts_loading)); setSearchNoResultsText(lang(lng_blocked_list_not_found)); - delegate()->peerListRefreshRows(); loadMoreRows(); + delegate()->peerListRefreshRows(); } void ParticipantsBoxController::loadMoreRows() { @@ -165,8 +182,14 @@ void ParticipantsBoxController::loadMoreRows() { return; } + if (feedMegagroupLastParticipants()) { + return; + } + auto filter = [this] { - if (_role == Role::Admins) { + if (_role == Role::Members) { + return MTP_channelParticipantsRecent(); + } else if (_role == Role::Admins) { return MTP_channelParticipantsAdmins(); } else if (_role == Role::Restricted) { return MTP_channelParticipantsBanned(MTP_string(QString())); @@ -185,26 +208,71 @@ void ParticipantsBoxController::loadMoreRows() { setDescriptionText((_role == Role::Restricted) ? lang(lng_group_blocked_list_about) : QString()); } auto &participants = result.c_channels_channelParticipants(); -App::feedUsers(participants.vusers); + App::feedUsers(participants.vusers); -auto &list = participants.vparticipants.v; -if (list.isEmpty()) { - // To be sure - wait for a whole empty result list. - _allLoaded = true; -} else { - for_const (auto &participant, list) { - ++_offset; - HandleParticipant(participant, _role, &_additional, [this](gsl::not_null user) { - appendRow(user); - }); - } -} -delegate()->peerListRefreshRows(); + auto &list = participants.vparticipants.v; + if (list.isEmpty()) { + // To be sure - wait for a whole empty result list. + _allLoaded = true; + } else { + for_const (auto &participant, list) { + ++_offset; + HandleParticipant(participant, _role, &_additional, [this](gsl::not_null user) { + appendRow(user); + }); + } + } + delegate()->peerListRefreshRows(); }).fail([this](const RPCError &error) { _loadRequestId = 0; }).send(); } +bool ParticipantsBoxController::feedMegagroupLastParticipants() { + if (_role != Role::Members || _offset > 0) { + return false; + } + auto megagroup = _channel->asMegagroup(); + if (!megagroup) { + return false; + } + auto info = megagroup->mgInfo.get(); + if (info->lastParticipantsStatus != MegagroupInfo::LastParticipantsUpToDate) { + return false; + } + if (info->lastParticipants.isEmpty()) { + return false; + } + + if (info->creator) { + _additional.creator = info->creator; + } + for_const (auto user, info->lastParticipants) { + auto admin = info->lastAdmins.constFind(user); + if (admin != info->lastAdmins.cend()) { + _additional.restrictedRights.erase(user); + if (admin->canEdit) { + _additional.adminCanEdit.emplace(user); + } else { + _additional.adminCanEdit.erase(user); + } + _additional.adminRights.emplace(user, admin->rights); + } else { + _additional.adminCanEdit.erase(user); + _additional.adminRights.erase(user); + auto restricted = info->lastRestricted.constFind(user); + if (restricted != info->lastRestricted.cend()) { + _additional.restrictedRights.emplace(user, restricted->rights); + } else { + _additional.restrictedRights.erase(user); + } + } + appendRow(user); + ++_offset; + } + return true; +} + void ParticipantsBoxController::rowClicked(gsl::not_null row) { auto user = row->peer()->asUser(); Expects(user != nullptr); @@ -222,7 +290,9 @@ void ParticipantsBoxController::rowActionClicked(gsl::not_null row auto user = row->peer()->asUser(); Expects(user != nullptr); - if (_role == Role::Admins) { + if (_role == Role::Members) { + kickMember(user); + } else if (_role == Role::Admins) { showAdmin(user); } else if (_role == Role::Restricted) { showRestricted(user); @@ -350,6 +420,30 @@ void ParticipantsBoxController::editRestrictedDone(gsl::not_null user delegate()->peerListRefreshRows(); } +void ParticipantsBoxController::kickMember(gsl::not_null user) { + auto text = (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel)(lt_user, user->firstName); + auto weak = base::weak_unique_ptr(this); + _editBox = Ui::show(Box(text, lang(lng_box_remove), [weak, user] { + if (weak) { + weak->kickMemberSure(user); + } + }), KeepOtherLayers); +} + +void ParticipantsBoxController::kickMemberSure(gsl::not_null user) { + if (_editBox) { + _editBox->closeBox(); + } + auto alreadyIt = _additional.restrictedRights.find(user); + auto currentRights = (alreadyIt == _additional.restrictedRights.cend()) ? MTP_channelBannedRights(MTP_flags(0), MTP_int(0)) : alreadyIt->second; + + if (auto row = delegate()->peerListFindRow(user->id)) { + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); + } + AuthSession::Current().api().kickParticipant(_channel, user, currentRights); +} + void ParticipantsBoxController::removeKicked(gsl::not_null row, gsl::not_null user) { delegate()->peerListRemoveRow(row); delegate()->peerListRefreshRows(); @@ -394,21 +488,27 @@ std::unique_ptr ParticipantsBoxController::createRow(gsl::not_null< auto row = std::make_unique(user); if (_role == Role::Admins) { auto promotedBy = _additional.adminPromotedBy.find(user); - if (promotedBy == _additional.adminPromotedBy.end()) { + if (promotedBy == _additional.adminPromotedBy.cend()) { row->setCustomStatus(lang(lng_channel_admin_status_creator)); } else { row->setCustomStatus(lng_channel_admin_status_promoted_by(lt_user, App::peerName(promotedBy->second))); } } - if (_role == Role::Restricted || (_role == Role::Admins && _additional.adminCanEdit.find(user) != _additional.adminCanEdit.end())) { + if (_role == Role::Restricted || (_role == Role::Admins && _additional.adminCanEdit.find(user) != _additional.adminCanEdit.cend())) { // row->setActionLink(lang(lng_profile_edit_permissions)); } else if (_role == Role::Kicked) { row->setActionLink(lang(lng_blocked_list_unblock)); + } else if (_role == Role::Members) { + if (_channel->canBanMembers() && _additional.creator != user + && (_additional.adminRights.find(user) == _additional.adminRights.cend() + || _additional.adminCanEdit.find(user) != _additional.adminCanEdit.cend())) { + row->setActionLink(lang(lng_profile_kick)); + } } return std::move(row); } -BannedBoxSearchController::BannedBoxSearchController(gsl::not_null channel, Role role, gsl::not_null additional) +ParticipantsBoxSearchController::ParticipantsBoxSearchController(gsl::not_null channel, Role role, gsl::not_null additional) : _channel(channel) , _role(role) , _additional(additional) { @@ -416,7 +516,7 @@ BannedBoxSearchController::BannedBoxSearchController(gsl::not_null _timer.setCallback([this] { searchOnServer(); }); } -void BannedBoxSearchController::searchQuery(const QString &query) { +void ParticipantsBoxSearchController::searchQuery(const QString &query) { if (_query != query) { _query = query; _offset = 0; @@ -430,16 +530,16 @@ void BannedBoxSearchController::searchQuery(const QString &query) { } } -void BannedBoxSearchController::searchOnServer() { +void ParticipantsBoxSearchController::searchOnServer() { Expects(!_query.isEmpty()); loadMoreRows(); } -bool BannedBoxSearchController::isLoading() { +bool ParticipantsBoxSearchController::isLoading() { return _timer.isActive() || _requestId; } -bool BannedBoxSearchController::searchInCache() { +bool ParticipantsBoxSearchController::searchInCache() { auto it = _cache.find(_query); if (it != _cache.cend()) { _requestId = 0; @@ -449,18 +549,25 @@ bool BannedBoxSearchController::searchInCache() { return false; } -bool BannedBoxSearchController::loadMoreRows() { +bool ParticipantsBoxSearchController::loadMoreRows() { if (_query.isEmpty()) { return false; } if (!_allLoaded && !isLoading()) { - auto filter = (_role == Role::Restricted) ? MTP_channelParticipantsBanned(MTP_string(_query)) : MTP_channelParticipantsKicked(MTP_string(_query)); + auto filter = [this] { + switch (_role) { + case Role::Members: return MTP_channelParticipantsSearch(MTP_string(_query)); + case Role::Restricted: return MTP_channelParticipantsBanned(MTP_string(_query)); + case Role::Kicked: return MTP_channelParticipantsKicked(MTP_string(_query)); + } + Unexpected("Role in ParticipantsBoxSearchController::loadMoreRows()"); + }; // For search we request a lot of rows from the first query. // (because we've waited for search request by timer already, // so we don't expect it to be fast, but we want to fill cache). auto perPage = kParticipantsPerPage; - _requestId = request(MTPchannels_GetParticipants(_channel->inputChannel, filter, MTP_int(_offset), MTP_int(perPage))).done([this, perPage](const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { + _requestId = request(MTPchannels_GetParticipants(_channel->inputChannel, filter(), MTP_int(_offset), MTP_int(perPage))).done([this, perPage](const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { searchDone(requestId, result, perPage); }).fail([this](const RPCError &error, mtpRequestId requestId) { if (_requestId == requestId) { @@ -478,7 +585,7 @@ bool BannedBoxSearchController::loadMoreRows() { return true; } -void BannedBoxSearchController::searchDone(mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount) { +void ParticipantsBoxSearchController::searchDone(mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount) { Expects(result.type() == mtpc_channels_channelParticipants); auto &participants = result.c_channels_channelParticipants(); diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.h b/Telegram/SourceFiles/profile/profile_channel_controllers.h index 0c6c55364..262d66cb5 100644 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.h +++ b/Telegram/SourceFiles/profile/profile_channel_controllers.h @@ -30,6 +30,7 @@ namespace Profile { class ParticipantsBoxController : public PeerListController, private base::Subscriber, private MTP::Sender, public base::enable_weak_from_this { public: enum class Role { + Members, Admins, Restricted, Kicked, @@ -69,10 +70,13 @@ private: void showRestricted(gsl::not_null user); void editRestrictedDone(gsl::not_null user, const MTPChannelBannedRights &rights); void removeKicked(gsl::not_null row, gsl::not_null user); + void kickMember(gsl::not_null user); + void kickMemberSure(gsl::not_null user); bool appendRow(gsl::not_null user); bool prependRow(gsl::not_null user); bool removeRow(gsl::not_null user); std::unique_ptr createRow(gsl::not_null user) const; + bool feedMegagroupLastParticipants(); gsl::not_null _channel; Role _role = Role::Admins; @@ -85,13 +89,13 @@ private: }; -// Banned and restricted users server side search. -class BannedBoxSearchController : public PeerListSearchController, private MTP::Sender { +// Members, banned and restricted users server side search. +class ParticipantsBoxSearchController : public PeerListSearchController, private MTP::Sender { public: using Role = ParticipantsBoxController::Role; using Additional = ParticipantsBoxController::Additional; - BannedBoxSearchController(gsl::not_null channel, Role role, gsl::not_null additional); + ParticipantsBoxSearchController(gsl::not_null channel, Role role, gsl::not_null additional); void searchQuery(const QString &query) override; bool isLoading() override;