From a605c110a8d1aae5b93e84e21a4f9eb757bdb396 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 10 Jan 2019 10:26:08 +0400 Subject: [PATCH] Support legacy groups in participant boxes. --- .../Resources/icons/info_restricted_users.png | Bin 354 -> 0 bytes .../icons/info_restricted_users@2x.png | Bin 737 -> 0 bytes .../icons/info_restricted_users@3x.png | Bin 1121 -> 0 bytes Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/apiwrap.cpp | 113 - Telegram/SourceFiles/apiwrap.h | 24 +- .../SourceFiles/boxes/add_contact_box.cpp | 5 +- .../boxes/peer_list_controllers.cpp | 175 -- .../SourceFiles/boxes/peer_list_controllers.h | 39 - .../boxes/peers/add_participants_box.cpp | 1099 +++++++++ .../boxes/peers/add_participants_box.h | 183 ++ .../boxes/peers/edit_participant_box.cpp | 77 +- .../boxes/peers/edit_participant_box.h | 17 +- .../boxes/peers/edit_participants_box.cpp | 1508 +++++++++++++ .../peers/edit_participants_box.h} | 163 +- .../boxes/peers/edit_peer_permissions_box.cpp | 49 +- .../boxes/peers/edit_peer_permissions_box.h | 4 +- .../boxes/peers/manage_peer_box.cpp | 36 +- .../SourceFiles/boxes/peers/manage_peer_box.h | 2 +- Telegram/SourceFiles/data/data_channel.cpp | 42 +- Telegram/SourceFiles/data/data_channel.h | 7 +- Telegram/SourceFiles/data/data_chat.cpp | 47 +- Telegram/SourceFiles/data/data_chat.h | 10 +- .../SourceFiles/data/data_media_types.cpp | 7 + Telegram/SourceFiles/data/data_media_types.h | 1 + Telegram/SourceFiles/data/data_peer.cpp | 5 +- .../dialogs_search_from_controllers.cpp | 1 + .../dialogs/dialogs_search_from_controllers.h | 4 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 2 +- .../admin_log/history_admin_log_item.cpp | 2 +- Telegram/SourceFiles/info/info.style | 3 +- .../info/profile/info_profile_members.cpp | 8 +- .../info/profile/info_profile_members.h | 4 - .../info_profile_members_controllers.cpp | 13 +- .../profile/profile_block_group_members.cpp | 47 +- .../profile/profile_block_group_members.h | 2 - .../profile/profile_channel_controllers.cpp | 2000 ----------------- .../SourceFiles/window/window_peer_menu.cpp | 6 +- Telegram/gyp/telegram_sources.txt | 6 +- 39 files changed, 3064 insertions(+), 2649 deletions(-) delete mode 100644 Telegram/Resources/icons/info_restricted_users.png delete mode 100644 Telegram/Resources/icons/info_restricted_users@2x.png delete mode 100644 Telegram/Resources/icons/info_restricted_users@3x.png create mode 100644 Telegram/SourceFiles/boxes/peers/add_participants_box.cpp create mode 100644 Telegram/SourceFiles/boxes/peers/add_participants_box.h create mode 100644 Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp rename Telegram/SourceFiles/{profile/profile_channel_controllers.h => boxes/peers/edit_participants_box.h} (59%) delete mode 100644 Telegram/SourceFiles/profile/profile_channel_controllers.cpp diff --git a/Telegram/Resources/icons/info_restricted_users.png b/Telegram/Resources/icons/info_restricted_users.png deleted file mode 100644 index 348a6a820f02ca412aa1b0a9f5d2cee93558882b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 354 zcmV-o0iFJdP)ptfx(&+}vm>~US!i1OaUd;hbps%qC+-`>8tEK2}D-}ewgfSJL} zkWyk81^|Fk>No(cH2^>e@l?CrZUBI$X^sO3A;5bNW`3$=S%z^OF-_BP08w*wW6Wax zl}{f-q7}lTDBzsKUUgl!HIU02QZA3O)~*6@q?m0F?qz1)*Vc{iot}Zu1t^MwoO9H5 z9Vw;$1d%lRzr*<|Y-|6F=;RK>kUc&powek-FHuUtS_@+gq?E}WxW@4P0i1J0bZ_Y% z6A@W!H$g0hB!+FT=EC{t)es)7_s_ru@b3Y>0CeYx;_cP+$^ZZW07*qoM6N<$f=5-I AuK)l5 diff --git a/Telegram/Resources/icons/info_restricted_users@2x.png b/Telegram/Resources/icons/info_restricted_users@2x.png deleted file mode 100644 index e059b2efe2df006682436042f9b32d5e86f772f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 737 zcmV<70v`Q|P)G4NqArApkm@#yj#1w1xtPfrb1M5zy2r1x z;DC{PyY??TE^`2Y@WGO(4Nyw}pf*4)0f5>7wFCfa1Jn`#s0~mnRRD^jK)cw@q5;QKzft_%HsA6l&zD2kG%B>?ypg%e+fVc>SV#b?@VHrQx1Qt|YAC3^u$lCazD zJ{|m4olXZONyM_HDG^JH1ZMx%j? z#UgTky*EXBYP;IZ*%N_vNbweB8E4Oi>P{7OO64b9&tLy>reLomrHk-X% z?>J6Sf4N)|1CUq}%;)pLajjN+x&C}U2gkB3zs1wPB;Ez64N&Xj0RX&VBhfSsZQDlA z^CC&^-C~TPWm%}I>i_W+&@>GXheP6n-)g_#zY79i1KYM!AO0%SG`Ruft`xp1&-0*I zEb_GYvKV7fsZ_Xna05ULm*;Xho_48U@8h2W0syrEY6$?;2B<{>K+`m^Z5uq#1I8Fw zmIbP+Qdj{1&LDBCXXoEbj&YOStJjOM(gpA+ImStL?*@>15PFmx<0QMx2cg>msFgZ^ z>2!McyUApdqLV*Lbq`Qg75Dpnq;9v{VYOQQ{+r+xf@zv~JRb3QJmPRTj5T}`0L0rM zSM@iL06=YkS^@yI0c!o=ZQywxluD)GbDBwyF~;44`wn=N?2^{Ik|S4;xR>mbR@~$m zV{tbKCA*~cYI1zGxRYSk_3=*u0f5>7wFCfa1Jn`#s0~m{0H8KNEdhYq0JVMs5_>^L TF?y6V00000NkvXXu0mjf1}9U5 diff --git a/Telegram/Resources/icons/info_restricted_users@3x.png b/Telegram/Resources/icons/info_restricted_users@3x.png deleted file mode 100644 index 1f846324bcff3382420dff9022c7afa18e2fe97b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1121 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>VA1e&aSW-L^LEZbzgG?-ZT?TA zW@&W3BXrr+lL;_u(f3;G`)R@)wSH z?B$m=kEdGAE!uteS&Sb0`}!WX_YIpuSBK8?QQN5g&wBF7mep6aYGhUlCq#s{wA6#gr*bh)OD)`f`)<{v{g>aC`A%hce_;BxmnCI(KfSHy^3~M1 zajyu`%HCgAwO4NUy3;>v=E)vUJYHxq|NQ3HyY9UHTKDI+M%Rv@l?RSzE@@c2SXr*u z?bJ!`!xOT%-nwPAHLCZpLC7`9UbmW>8Xo!eg-lUvrxjhYIsZ6lrO58El_5#zKTr7@ zwN|WVj~nNS6eAnC{;BEz)F%IrYF;QzkmNwC@d%~cq}$1X3d{H*I(zxMu$HB^r`6e zt=&v@Vk;iCIW62!`W8rR`j%qU*gxB6C)2c*&&m%O+}ZW*_3PKC&iniOZ(p~UTj!Sn zV{2upI3FM1l-rMzBE?(-m)vPP)}j5tT$$-W2ZMnULk|bTW^=}ExxtGsD%}5`X*OHX z_h6aut8V?Xp#B^_~JObIzt+)?W~}e(`f7!>?7byBXeoZ+QJW`)>P%V|KsR*>*+f zh*j@j`L^q5l4IZF4BNIc#y`&IK41P;`@xdi>8b05Qih^W(~rA chat) { - _chatAdminsEnabledRequests.take( - chat - ) | requestCanceller(); - - _chatAdminsSaveRequests.take( - chat - ) | [&](auto &&requests) { - ranges::for_each(std::move(requests), requestCanceller()); - }; - - _chatAdminsToSave.remove(chat); -} - -void ApiWrap::editChatAdmins( - not_null chat, - bool adminsEnabled, - base::flat_set> &&admins) { - cancelEditChatAdmins(chat); - if (adminsEnabled) { - _chatAdminsToSave.emplace(chat, std::move(admins)); - } - - // #TODO groups - //auto requestId = request(MTPmessages_ToggleChatAdmins(chat->inputChat, MTP_bool(adminsEnabled))).done([this, chat](const MTPUpdates &updates) { - // _chatAdminsEnabledRequests.remove(chat); - // applyUpdates(updates); - // saveChatAdmins(chat); - //}).fail([this, chat](const RPCError &error) { - // _chatAdminsEnabledRequests.remove(chat); - // if (error.type() == qstr("CHAT_NOT_MODIFIED")) { - // saveChatAdmins(chat); - // } - //}).send(); - //_chatAdminsEnabledRequests.emplace(chat, requestId); -} - -void ApiWrap::saveChatAdmins(not_null chat) { - if (!_chatAdminsToSave.contains(chat)) { - return; - } - auto requestId = request(MTPmessages_GetFullChat(chat->inputChat)).done([this, chat](const MTPmessages_ChatFull &result) { - _chatAdminsEnabledRequests.remove(chat); - processFullPeer(chat, result); - sendSaveChatAdminsRequests(chat); - }).fail([this, chat](const RPCError &error) { - _chatAdminsEnabledRequests.remove(chat); - _chatAdminsToSave.remove(chat); - }).send(); - _chatAdminsEnabledRequests.emplace(chat, requestId); -} - -void ApiWrap::sendSaveChatAdminsRequests(not_null chat) { - auto editOne = [this, chat](not_null user, bool admin) { - auto requestId = request(MTPmessages_EditChatAdmin( - chat->inputChat, - user->inputUser, - MTP_bool(admin))) - .done([this, chat, user, admin]( - const MTPBool &result, - mtpRequestId requestId) { - _chatAdminsSaveRequests[chat].remove(requestId); - if (_chatAdminsSaveRequests[chat].empty()) { - _chatAdminsSaveRequests.remove(chat); - Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged); - } - if (mtpIsTrue(result)) { - if (admin) { - if (chat->noParticipantInfo()) { - requestFullPeer(chat); - } else { - chat->admins.insert(user); - } - } else { - chat->admins.remove(user); - } - } - }).fail([this, chat]( - const RPCError &error, - mtpRequestId requestId) { - _chatAdminsSaveRequests[chat].remove(requestId); - if (_chatAdminsSaveRequests[chat].empty()) { - _chatAdminsSaveRequests.remove(chat); - } - chat->invalidateParticipants(); - if (error.type() == qstr("USER_RESTRICTED")) { - Ui::show(Box(lang(lng_cant_do_this))); - } - }).afterDelay(kSmallDelayMs).send(); - - _chatAdminsSaveRequests[chat].insert(requestId); - }; - auto appointOne = [&](auto user) { editOne(user, true); }; - auto removeOne = [&](auto user) { editOne(user, false); }; - - auto admins = _chatAdminsToSave.take(chat); - Assert(!!admins); - - auto toRemove = chat->admins; - auto toAppoint = std::vector>(); - if (!admins->empty()) { - toAppoint.reserve(admins->size()); - for (auto user : *admins) { - if (!toRemove.remove(user) && user->id != peerFromUser(chat->creator)) { - toAppoint.push_back(user); - } - } - } - ranges::for_each(toRemove, removeOne); - ranges::for_each(toAppoint, appointOne); - requestSendDelayed(); -} - void ApiWrap::requestSharedMediaCount( not_null peer, Storage::SharedMediaType type) { diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index deef4d6a0..253918191 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -66,7 +66,7 @@ inline int32 CountHash(IntRange &&range) { } // namespace Api -class ApiWrap : private MTP::Sender, private base::Subscriber { +class ApiWrap : public MTP::Sender, private base::Subscriber { public: ApiWrap(not_null session); @@ -221,12 +221,9 @@ public: void jumpToDate(Dialogs::Key chat, const QDate &date); void preloadEnoughUnreadMentions(not_null history); - void checkForUnreadMentions(const base::flat_set &possiblyReadMentions, ChannelData *channel = nullptr); - - void editChatAdmins( - not_null chat, - bool adminsEnabled, - base::flat_set> &&admins); + void checkForUnreadMentions( + const base::flat_set &possiblyReadMentions, + ChannelData *channel = nullptr); using SliceType = Data::LoadDirection; void requestSharedMedia( @@ -490,9 +487,6 @@ private: void requestSavedGifs(TimeId now); void readFeaturedSets(); - void cancelEditChatAdmins(not_null chat); - void saveChatAdmins(not_null chat); - void sendSaveChatAdminsRequests(not_null chat); void refreshChannelAdmins( not_null channel, const QVector &participants); @@ -673,16 +667,6 @@ private: base::flat_map, mtpRequestId> _unreadMentionsRequests; - base::flat_map< - not_null, - mtpRequestId> _chatAdminsEnabledRequests; - base::flat_map< - not_null, - base::flat_set>> _chatAdminsToSave; - base::flat_map< - not_null, - base::flat_set> _chatAdminsSaveRequests; - base::flat_map, SharedMediaType, diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index eb2aaab30..8a5c4dc91 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -16,9 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "boxes/photo_crop_box.h" #include "boxes/peer_list_controllers.h" +#include "boxes/peers/add_participants_box.h" #include "boxes/peers/edit_participant_box.h" +#include "boxes/peers/edit_participants_box.h" #include "core/file_utilities.h" -#include "profile/profile_channel_controllers.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" @@ -91,7 +92,7 @@ void ShowAddParticipantsError( (*weak)->closeBox(); } }; - const auto saveCallback = Profile::SaveAdminCallback( + const auto saveCallback = SaveAdminCallback( channel, user, [=](auto&&...) { close(); }, diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 86f400a28..76e08f1e6 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -25,26 +25,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -base::flat_set> GetAlreadyInFromPeer(PeerData *peer) { - if (!peer) { - return {}; - } - if (auto chat = peer->asChat()) { - auto participants = ( - chat->participants - ) | ranges::view::transform([](auto &&pair) -> not_null { - return pair.first; - }); - return { participants.begin(), participants.end() }; - } else if (auto channel = peer->asChannel()) { - if (channel->isMegagroup()) { - auto &participants = channel->mgInfo->lastParticipants; - return { participants.cbegin(), participants.cend() }; - } - } - return {}; -} - void ShareBotGame(not_null bot, not_null chat) { const auto history = App::historyLoaded(chat); const auto randomId = rand_value(); @@ -83,25 +63,6 @@ void AddBotToGroup(not_null bot, not_null chat) { Ui::showPeerHistory(chat, ShowAtUnreadMsgId); } -bool InviteSelectedUsers( - not_null box, - not_null chat) { - const auto rows = box->peerListCollectSelectedRows(); - const auto users = ranges::view::all( - rows - ) | ranges::view::transform([](not_null peer) { - Expects(peer->isUser()); - Expects(!peer->isSelf()); - - return not_null(peer->asUser()); - }) | ranges::to_vector; - if (users.empty()) { - return false; - } - Auth().api().addChatParticipants(chat, users); - return true; -} - } // namespace // Not used for now. @@ -421,142 +382,6 @@ std::unique_ptr ContactsBoxController::createRow(not_null(user); } -AddParticipantsBoxController::AddParticipantsBoxController(PeerData *peer) -: ContactsBoxController(std::make_unique()) -, _peer(peer) -, _alreadyIn(GetAlreadyInFromPeer(peer)) { -} - -AddParticipantsBoxController::AddParticipantsBoxController( - not_null channel, - base::flat_set> &&alreadyIn) -: ContactsBoxController(std::make_unique()) -, _peer(channel) -, _alreadyIn(std::move(alreadyIn)) { -} - -void AddParticipantsBoxController::rowClicked(not_null row) { - auto count = fullCount(); - auto limit = (_peer && _peer->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax(); - if (count < limit || row->checked()) { - delegate()->peerListSetRowChecked(row, !row->checked()); - updateTitle(); - } else if (auto channel = _peer ? _peer->asChannel() : nullptr) { - if (!_peer->isMegagroup()) { - Ui::show( - Box(_peer->asChannel()), - LayerOption::KeepOther); - } - } else if (count >= Global::ChatSizeMax() && count < Global::MegagroupSizeMax()) { - Ui::show( - Box(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), - LayerOption::KeepOther); - } -} - -void AddParticipantsBoxController::itemDeselectedHook(not_null peer) { - updateTitle(); -} - -void AddParticipantsBoxController::prepareViewHook() { - updateTitle(); -} - -int AddParticipantsBoxController::alreadyInCount() const { - if (!_peer) { - return 1; // self - } - if (auto chat = _peer->asChat()) { - return qMax(chat->count, 1); - } else if (auto channel = _peer->asChannel()) { - return qMax(channel->membersCount(), int(_alreadyIn.size())); - } - Unexpected("User in AddParticipantsBoxController::alreadyInCount"); -} - -bool AddParticipantsBoxController::isAlreadyIn(not_null user) const { - if (!_peer) { - return false; - } - if (auto chat = _peer->asChat()) { - return chat->participants.contains(user); - } else if (auto channel = _peer->asChannel()) { - return _alreadyIn.contains(user) - || (channel->isMegagroup() && base::contains(channel->mgInfo->lastParticipants, user)); - } - Unexpected("User in AddParticipantsBoxController::isAlreadyIn"); -} - -int AddParticipantsBoxController::fullCount() const { - return alreadyInCount() + delegate()->peerListSelectedRowsCount(); -} - -std::unique_ptr AddParticipantsBoxController::createRow(not_null user) { - if (user->isSelf()) { - return nullptr; - } - auto result = std::make_unique(user); - if (isAlreadyIn(user)) { - result->setDisabledState(PeerListRow::State::DisabledChecked); - } - return result; -} - -void AddParticipantsBoxController::updateTitle() { - auto additional = (_peer && _peer->isChannel() && !_peer->isMegagroup()) - ? QString() : - QString("%1 / %2").arg(fullCount()).arg(Global::MegagroupSizeMax()); - delegate()->peerListSetTitle(langFactory(lng_profile_add_participant)); - delegate()->peerListSetAdditionalTitle([additional] { return additional; }); -} - -void AddParticipantsBoxController::Start(not_null chat) { - auto initBox = [=](not_null box) { - box->addButton(langFactory(lng_participant_invite), [=] { - if (InviteSelectedUsers(box, chat)) { - Ui::showPeerHistory(chat, ShowAtTheEndMsgId); - } - }); - box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); - }; - Ui::show(Box(std::make_unique(chat), std::move(initBox))); -} - -void AddParticipantsBoxController::Start( - not_null channel, - base::flat_set> &&alreadyIn, - bool justCreated) { - auto initBox = [channel, justCreated](not_null box) { - auto subscription = std::make_shared(); - box->addButton(langFactory(lng_participant_invite), [=, copy = subscription] { - if (InviteSelectedUsers(box, channel)) { - if (channel->isMegagroup()) { - Ui::showPeerHistory(channel, ShowAtTheEndMsgId); - } else { - box->closeBox(); - } - } - }); - box->addButton(langFactory(justCreated ? lng_create_group_skip : lng_cancel), [box] { box->closeBox(); }); - if (justCreated) { - box->boxClosing() | rpl::start_with_next([=] { - Ui::showPeerHistory(channel, ShowAtTheEndMsgId); - }, *subscription); - } - }; - Ui::show(Box(std::make_unique(channel, std::move(alreadyIn)), std::move(initBox))); -} - -void AddParticipantsBoxController::Start( - not_null channel, - base::flat_set> &&alreadyIn) { - Start(channel, std::move(alreadyIn), false); -} - -void AddParticipantsBoxController::Start(not_null channel) { - Start(channel, {}, true); -} - void AddBotToGroupBoxController::Start(not_null bot) { auto initBox = [=](not_null box) { box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index 0a58d9543..c0f7fd699 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -136,45 +136,6 @@ private: }; -class AddParticipantsBoxController : public ContactsBoxController { -public: - static void Start(not_null chat); - static void Start(not_null channel); - static void Start( - not_null channel, - base::flat_set> &&alreadyIn); - - AddParticipantsBoxController(PeerData *peer); - AddParticipantsBoxController( - not_null channel, - base::flat_set> &&alreadyIn); - - using ContactsBoxController::ContactsBoxController; - - void rowClicked(not_null row) override; - void itemDeselectedHook(not_null peer) override; - -protected: - void prepareViewHook() override; - std::unique_ptr createRow(not_null user) override; - -private: - static void Start( - not_null channel, - base::flat_set> &&alreadyIn, - bool justCreated); - - int alreadyInCount() const; - bool isAlreadyIn(not_null user) const; - int fullCount() const; - void updateTitle(); - bool inviteSelectedUsers(not_null chat) const; - - PeerData *_peer = nullptr; - base::flat_set> _alreadyIn; - -}; - class AddBotToGroupBoxController : public ChatsListBoxController , public base::has_weak_ptr { diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp new file mode 100644 index 000000000..e02c3fe77 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -0,0 +1,1099 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/peers/add_participants_box.h" + +#include "boxes/peers/edit_participant_box.h" +#include "boxes/confirm_box.h" +#include "lang/lang_keys.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_user.h" +#include "history/history.h" +#include "dialogs/dialogs_indexed_list.h" +#include "core/tl_help.h" +#include "base/overload.h" +#include "auth_session.h" +#include "mainwidget.h" +#include "apiwrap.h" +#include "observer_peer.h" + +namespace { + +constexpr auto kParticipantsFirstPageCount = 16; +constexpr auto kParticipantsPerPage = 200; + +base::flat_set> GetAlreadyInFromPeer(PeerData *peer) { + if (!peer) { + return {}; + } + if (const auto chat = peer->asChat()) { + const auto participants = ( + chat->participants + ) | ranges::view::transform([](auto &&pair) -> not_null { + return pair.first; + }); + return { participants.begin(), participants.end() }; + } else if (const auto channel = peer->asChannel()) { + if (channel->isMegagroup()) { + const auto &participants = channel->mgInfo->lastParticipants; + return { participants.cbegin(), participants.cend() }; + } + } + return {}; +} + +bool InviteSelectedUsers( + not_null box, + not_null chat) { + const auto rows = box->peerListCollectSelectedRows(); + const auto users = ranges::view::all( + rows + ) | ranges::view::transform([](not_null peer) { + Expects(peer->isUser()); + Expects(!peer->isSelf()); + + return not_null(peer->asUser()); + }) | ranges::to_vector; + if (users.empty()) { + return false; + } + Auth().api().addChatParticipants(chat, users); + return true; +} + +} // namespace + +AddParticipantsBoxController::AddParticipantsBoxController(PeerData *peer) +: ContactsBoxController(std::make_unique()) +, _peer(peer) +, _alreadyIn(GetAlreadyInFromPeer(peer)) { +} + +AddParticipantsBoxController::AddParticipantsBoxController( + not_null channel, + base::flat_set> &&alreadyIn) +: ContactsBoxController(std::make_unique()) +, _peer(channel) +, _alreadyIn(std::move(alreadyIn)) { +} + +void AddParticipantsBoxController::rowClicked(not_null row) { + auto count = fullCount(); + auto limit = (_peer && _peer->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax(); + if (count < limit || row->checked()) { + delegate()->peerListSetRowChecked(row, !row->checked()); + updateTitle(); + } else if (auto channel = _peer ? _peer->asChannel() : nullptr) { + if (!_peer->isMegagroup()) { + Ui::show( + Box(_peer->asChannel()), + LayerOption::KeepOther); + } + } else if (count >= Global::ChatSizeMax() && count < Global::MegagroupSizeMax()) { + Ui::show( + Box(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), + LayerOption::KeepOther); + } +} + +void AddParticipantsBoxController::itemDeselectedHook(not_null peer) { + updateTitle(); +} + +void AddParticipantsBoxController::prepareViewHook() { + updateTitle(); +} + +int AddParticipantsBoxController::alreadyInCount() const { + if (!_peer) { + return 1; // self + } + if (auto chat = _peer->asChat()) { + return qMax(chat->count, 1); + } else if (auto channel = _peer->asChannel()) { + return qMax(channel->membersCount(), int(_alreadyIn.size())); + } + Unexpected("User in AddParticipantsBoxController::alreadyInCount"); +} + +bool AddParticipantsBoxController::isAlreadyIn(not_null user) const { + if (!_peer) { + return false; + } + if (auto chat = _peer->asChat()) { + return chat->participants.contains(user); + } else if (auto channel = _peer->asChannel()) { + return _alreadyIn.contains(user) + || (channel->isMegagroup() && base::contains(channel->mgInfo->lastParticipants, user)); + } + Unexpected("User in AddParticipantsBoxController::isAlreadyIn"); +} + +int AddParticipantsBoxController::fullCount() const { + return alreadyInCount() + delegate()->peerListSelectedRowsCount(); +} + +std::unique_ptr AddParticipantsBoxController::createRow( + not_null user) { + if (user->isSelf()) { + return nullptr; + } + auto result = std::make_unique(user); + if (isAlreadyIn(user)) { + result->setDisabledState(PeerListRow::State::DisabledChecked); + } + return result; +} + +void AddParticipantsBoxController::updateTitle() { + auto additional = (_peer && _peer->isChannel() && !_peer->isMegagroup()) + ? QString() : + QString("%1 / %2").arg(fullCount()).arg(Global::MegagroupSizeMax()); + delegate()->peerListSetTitle(langFactory(lng_profile_add_participant)); + delegate()->peerListSetAdditionalTitle([additional] { return additional; }); +} + +void AddParticipantsBoxController::Start(not_null chat) { + auto initBox = [=](not_null box) { + box->addButton(langFactory(lng_participant_invite), [=] { + if (InviteSelectedUsers(box, chat)) { + Ui::showPeerHistory(chat, ShowAtTheEndMsgId); + } + }); + box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); + }; + Ui::show(Box(std::make_unique(chat), std::move(initBox))); +} + +void AddParticipantsBoxController::Start( + not_null channel, + base::flat_set> &&alreadyIn, + bool justCreated) { + auto initBox = [channel, justCreated](not_null box) { + auto subscription = std::make_shared(); + box->addButton(langFactory(lng_participant_invite), [=, copy = subscription] { + if (InviteSelectedUsers(box, channel)) { + if (channel->isMegagroup()) { + Ui::showPeerHistory(channel, ShowAtTheEndMsgId); + } else { + box->closeBox(); + } + } + }); + box->addButton(langFactory(justCreated ? lng_create_group_skip : lng_cancel), [box] { box->closeBox(); }); + if (justCreated) { + box->boxClosing() | rpl::start_with_next([=] { + Ui::showPeerHistory(channel, ShowAtTheEndMsgId); + }, *subscription); + } + }; + Ui::show(Box(std::make_unique(channel, std::move(alreadyIn)), std::move(initBox))); +} + +void AddParticipantsBoxController::Start( + not_null channel, + base::flat_set> &&alreadyIn) { + Start(channel, std::move(alreadyIn), false); +} + +void AddParticipantsBoxController::Start(not_null channel) { + Start(channel, {}, true); +} + +AddSpecialBoxController::AddSpecialBoxController( + not_null peer, + Role role, + AdminDoneCallback adminDoneCallback, + BannedDoneCallback bannedDoneCallback) +: PeerListController(std::make_unique( + peer, + &_additional)) +, _peer(peer) +, _role(role) +, _adminDoneCallback(std::move(adminDoneCallback)) +, _bannedDoneCallback(std::move(bannedDoneCallback)) { + _additional.fillCreator(_peer); +} + +std::unique_ptr AddSpecialBoxController::createSearchRow(not_null peer) { + if (peer->isSelf()) { + return nullptr; + } + if (const auto user = peer->asUser()) { + return createRow(user); + } + return nullptr; +} + +void AddSpecialBoxController::prepare() { + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + const auto title = [&] { + switch (_role) { + case Role::Admins: return langFactory(lng_channel_add_admin); + case Role::Restricted: return langFactory(lng_channel_add_restricted); + case Role::Kicked: return langFactory(lng_channel_add_banned); + } + Unexpected("Role in AddSpecialBoxController::prepare()"); + }(); + delegate()->peerListSetTitle(title); + setDescriptionText(lang(lng_contacts_loading)); + setSearchNoResultsText(lang(lng_blocked_list_not_found)); + + if (const auto chat = _peer->asChat()) { + prepareChatRows(chat); + } else { + loadMoreRows(); + } + delegate()->peerListRefreshRows(); +} + +void AddSpecialBoxController::prepareChatRows(not_null chat) { + _onlineSorter = std::make_unique( + chat, + delegate()); + + rebuildChatRows(chat); + if (!delegate()->peerListFullRowsCount()) { + chat->updateFullForced(); + } + + using UpdateFlag = Notify::PeerUpdate::Flag; + subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler( + UpdateFlag::MembersChanged, + [=](const Notify::PeerUpdate &update) { + if (update.flags & UpdateFlag::MembersChanged) { + if (update.peer == chat) { + rebuildChatRows(chat); + } + } + })); +} + +void AddSpecialBoxController::rebuildChatRows(not_null chat) { + if (chat->participants.empty()) { + // We get such updates often + // (when participants list was invalidated). + //while (delegate()->peerListFullRowsCount() > 0) { + // delegate()->peerListRemoveRow( + // delegate()->peerListRowAt(0)); + //} + return; + } + + auto &participants = chat->participants; + auto count = delegate()->peerListFullRowsCount(); + for (auto i = 0; i != count;) { + auto row = delegate()->peerListRowAt(i); + auto user = row->peer()->asUser(); + if (participants.contains(user)) { + ++i; + } else { + delegate()->peerListRemoveRow(row); + --count; + } + } + for (const auto [user, v] : participants) { + if (auto row = createRow(user)) { + delegate()->peerListAppendRow(std::move(row)); + } + } + _onlineSorter->sort(); + + delegate()->peerListRefreshRows(); +} + +void AddSpecialBoxController::loadMoreRows() { + if (searchController() && searchController()->loadMoreRows()) { + return; + } else if (!_peer->isChannel() || _loadRequestId || _allLoaded) { + return; + } + + // First query is small and fast, next loads a lot of rows. + const auto perPage = (_offset > 0) + ? kParticipantsPerPage + : kParticipantsFirstPageCount; + const auto participantsHash = 0; + const auto channel = _peer->asChannel(); + + _loadRequestId = request(MTPchannels_GetParticipants( + channel->inputChannel, + MTP_channelParticipantsRecent(), + MTP_int(_offset), + MTP_int(perPage), + MTP_int(participantsHash) + )).done([=](const MTPchannels_ChannelParticipants &result) { + _loadRequestId = 0; + + Auth().api().parseChannelParticipants(channel, result, [&]( + int availableCount, + const QVector &list) { + for (auto &participant : list) { + HandleParticipant( + participant, + &_additional, + [&](auto user) { appendRow(user); }); + } + if (auto size = list.size()) { + _offset += size; + } else { + // To be sure - wait for a whole empty result list. + _allLoaded = true; + } + }); + + if (delegate()->peerListFullRowsCount() > 0) { + setDescriptionText(QString()); + } else if (_allLoaded) { + setDescriptionText(lang(lng_blocked_list_not_found)); + } + delegate()->peerListRefreshRows(); + }).fail([this](const RPCError &error) { + _loadRequestId = 0; + }).send(); +} + +void AddSpecialBoxController::rowClicked(not_null row) { + auto user = row->peer()->asUser(); + switch (_role) { + case Role::Admins: return showAdmin(user); + case Role::Restricted: return showRestricted(user); + case Role::Kicked: return kickUser(user); + } + Unexpected("Role in AddSpecialBoxController::rowClicked()"); +} + +template +bool AddSpecialBoxController::checkInfoLoaded( + not_null user, + Callback callback) { + if (_peer->isChat() + || (_additional.infoNotLoaded.find(user) + == _additional.infoNotLoaded.end())) { + return true; + } + + // We don't know what this user status is in the group. + const auto channel = _peer->asChannel(); + request(MTPchannels_GetParticipant( + channel->inputChannel, + user->inputUser + )).done([=](const MTPchannels_ChannelParticipant &result) { + Expects(result.type() == mtpc_channels_channelParticipant); + + const auto &participant = result.c_channels_channelParticipant(); + App::feedUsers(participant.vusers); + HandleParticipant( + participant.vparticipant, + &_additional, + [](not_null) {}); + _additional.infoNotLoaded.erase(user); + callback(); + }).fail([this, user, callback](const RPCError &error) { + _additional.infoNotLoaded.erase(user); + _additional.external.emplace(user); + callback(); + }).send(); + return false; +} + +void AddSpecialBoxController::showAdmin( + not_null user, + bool sure) { + if (!checkInfoLoaded(user, [=] { showAdmin(user); })) { + return; + } + + if (sure && _editBox) { + // Close the confirmation box. + _editBox->closeBox(); + } + + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + + // Check restrictions. + const auto canAddMembers = chat + ? chat->canAddMembers() + : channel->canAddMembers(); + const auto canBanMembers = chat + ? chat->canBanMembers() + : channel->canBanMembers(); + const auto weak = base::make_weak(this); + const auto alreadyIt = _additional.adminRights.find(user); + auto currentRights = (_additional.creator == user) + ? MTP_chatAdminRights(MTP_flags(~MTPDchatAdminRights::Flag::f_add_admins | MTPDchatAdminRights::Flag::f_add_admins)) + : MTP_chatAdminRights(MTP_flags(0)); + if (alreadyIt != _additional.adminRights.end()) { + // The user is already an admin. + currentRights = alreadyIt->second; + } else if (_additional.kicked.find(user) != _additional.kicked.end()) { + // The user is banned. + if (canAddMembers) { + if (canBanMembers) { + if (!sure) { + _editBox = Ui::show(Box(lang(lng_sure_add_admin_unban), [weak, user] { + if (weak) { + weak->showAdmin(user, true); + } + }), LayerOption::KeepOther); + return; + } + } else { + Ui::show(Box( + lang(lng_error_cant_add_admin_unban)), + LayerOption::KeepOther); + return; + } + } else { + Ui::show(Box( + lang(lng_error_cant_add_admin_invite)), + LayerOption::KeepOther); + return; + } + } else if (_additional.restrictedRights.find(user) != _additional.restrictedRights.end()) { + // The user is restricted. + if (canBanMembers) { + if (!sure) { + _editBox = Ui::show(Box(lang(lng_sure_add_admin_unban), [weak, user] { + if (weak) { + weak->showAdmin(user, true); + } + }), LayerOption::KeepOther); + return; + } + } else { + Ui::show(Box( + lang(lng_error_cant_add_admin_unban)), + LayerOption::KeepOther); + return; + } + } else if (_additional.external.find(user) != _additional.external.end()) { + // The user is not in the group yet. + if (canAddMembers) { + if (!sure) { + const auto text = lang( + ((_peer->isChat() || _peer->isMegagroup()) + ? lng_sure_add_admin_invite + : lng_sure_add_admin_invite_channel)); + _editBox = Ui::show(Box(text, [=] { + if (weak) { + weak->showAdmin(user, true); + } + }), LayerOption::KeepOther); + return; + } + } else { + Ui::show( + Box(lang(lng_error_cant_add_admin_invite)), + LayerOption::KeepOther); + return; + } + } + + // Finally show the admin. + auto canNotEdit = (_additional.creator == user) + || ((alreadyIt != _additional.adminRights.end()) + && (_additional.adminCanEdit.find(user) == _additional.adminCanEdit.end())); + auto box = Box(_peer, user, currentRights); + if (!canNotEdit) { + if (chat) { + // #TODO groups autoconv + } else { + box->setSaveCallback(SaveAdminCallback(channel, user, [=]( + const MTPChatAdminRights &newRights) { + if (weak) { + weak->editAdminDone(user, newRights); + } + }, [=] { + if (weak && weak->_editBox) { + weak->_editBox->closeBox(); + } + })); + } + } + _editBox = Ui::show(std::move(box), LayerOption::KeepOther); +} + +void AddSpecialBoxController::editAdminDone( + not_null user, + const MTPChatAdminRights &rights) { + if (_editBox) _editBox->closeBox(); + _additional.restrictedRights.erase(user); + _additional.restrictedBy.erase(user); + _additional.kicked.erase(user); + _additional.external.erase(user); + if (rights.c_chatAdminRights().vflags.v == 0) { + _additional.adminRights.erase(user); + _additional.adminPromotedBy.erase(user); + _additional.adminCanEdit.erase(user); + } else { + _additional.adminRights[user] = rights; + _additional.adminCanEdit.emplace(user); + auto it = _additional.adminPromotedBy.find(user); + if (it == _additional.adminPromotedBy.end()) { + _additional.adminPromotedBy.emplace(user, Auth().user()); + } + } + if (_adminDoneCallback) { + _adminDoneCallback(user, rights); + } +} + +void AddSpecialBoxController::showRestricted( + not_null user, + bool sure) { + if (!checkInfoLoaded(user, [=] { showRestricted(user); })) { + return; + } + + if (sure && _editBox) { + // Close the confirmation box. + _editBox->closeBox(); + } + + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + + // Check restrictions. + const auto weak = base::make_weak(this); + const auto alreadyIt = _additional.restrictedRights.find(user); + auto currentRights = MTP_chatBannedRights(MTP_flags(0), MTP_int(0)); + auto hasAdminRights = false; + if (alreadyIt != _additional.restrictedRights.end()) { + // The user is already banned or restricted. + currentRights = alreadyIt->second; + } else if (_additional.adminRights.find(user) != _additional.adminRights.end() || _additional.creator == user) { + // The user is an admin or creator. + if (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()) { + hasAdminRights = true; + if (!sure) { + _editBox = Ui::show(Box(lang(lng_sure_ban_admin), [weak, user] { + if (weak) { + weak->showRestricted(user, true); + } + }), LayerOption::KeepOther); + return; + } + } else { + Ui::show( + Box(lang(lng_error_cant_ban_admin)), + LayerOption::KeepOther); + return; + } + } + + // Finally edit the restricted. + auto box = Box(_peer, user, hasAdminRights, currentRights); + if (chat) { + // #TODO groups autoconv + } else { + box->setSaveCallback(SaveRestrictedCallback(channel, user, [=]( + const MTPChatBannedRights &newRights) { + if (const auto strong = weak.get()) { + strong->editRestrictedDone(user, newRights); + } + }, [=] { + if (weak && weak->_editBox) { + weak->_editBox->closeBox(); + } + })); + } + _editBox = Ui::show(std::move(box), LayerOption::KeepOther); +} + +void AddSpecialBoxController::editRestrictedDone( + not_null user, + const MTPChatBannedRights &rights) { + if (_editBox) _editBox->closeBox(); + _additional.adminRights.erase(user); + _additional.adminCanEdit.erase(user); + _additional.adminPromotedBy.erase(user); + if (rights.c_chatBannedRights().vflags.v == 0) { + _additional.restrictedRights.erase(user); + _additional.restrictedBy.erase(user); + _additional.kicked.erase(user); + } else { + _additional.restrictedRights[user] = rights; + if (rights.c_chatBannedRights().vflags.v & MTPDchatBannedRights::Flag::f_view_messages) { + _additional.kicked.emplace(user); + } else { + _additional.kicked.erase(user); + } + _additional.restrictedBy.emplace(user, Auth().user()); + } + if (_bannedDoneCallback) { + _bannedDoneCallback(user, rights); + } +} + +void AddSpecialBoxController::kickUser( + not_null user, + bool sure) { + if (!checkInfoLoaded(user, [=] { kickUser(user); })) { + return; + } + + // Check restrictions. + auto weak = base::make_weak(this); + if (_additional.adminRights.find(user) != _additional.adminRights.end() || _additional.creator == user) { + // The user is an admin or creator. + if (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()) { + if (!sure) { + _editBox = Ui::show(Box(lang(lng_sure_ban_admin), [weak, user] { + if (weak) { + weak->kickUser(user, true); + } + }), LayerOption::KeepOther); + return; + } + } else { + Ui::show( + Box(lang(lng_error_cant_ban_admin)), + LayerOption::KeepOther); + return; + } + } + + // Finally kick him. + if (!sure) { + const auto text = ((_peer->isChat() || _peer->isMegagroup()) + ? lng_sure_ban_user_group + : lng_sure_ban_user_channel)(lt_user, App::peerName(user)); + _editBox = Ui::show(Box(text, [=] { + if (weak) { + weak->kickUser(user, true); + } + }), LayerOption::KeepOther); + return; + } + auto currentRights = MTP_chatBannedRights(MTP_flags(0), MTP_int(0)); + auto alreadyIt = _additional.restrictedRights.find(user); + if (alreadyIt != _additional.restrictedRights.end()) { + // The user is already banned or restricted. + currentRights = alreadyIt->second; + } + if (const auto chat = _peer->asChat()) { + // #TODO groups + } else if (const auto channel = _peer->asChannel()) { + const auto callback = SaveRestrictedCallback(channel, user, []( + const MTPChatBannedRights &newRights) {}, [] {}); + callback(currentRights, ChannelData::KickedRestrictedRights()); + } +} + +bool AddSpecialBoxController::appendRow(not_null user) { + if (delegate()->peerListFindRow(user->id) || user->isSelf()) { + return false; + } + delegate()->peerListAppendRow(createRow(user)); + return true; +} + +bool AddSpecialBoxController::prependRow(not_null user) { + if (delegate()->peerListFindRow(user->id)) { + return false; + } + delegate()->peerListPrependRow(createRow(user)); + return true; +} + +std::unique_ptr AddSpecialBoxController::createRow( + not_null user) const { + return std::make_unique(user); +} + +template +void AddSpecialBoxController::HandleParticipant( + const MTPChannelParticipant &participant, + not_null additional, + Callback callback) { + switch (participant.type()) { + case mtpc_channelParticipantAdmin: { + auto &admin = participant.c_channelParticipantAdmin(); + if (auto user = App::userLoaded(admin.vuser_id.v)) { + additional->infoNotLoaded.erase(user); + additional->restrictedRights.erase(user); + additional->kicked.erase(user); + additional->restrictedBy.erase(user); + additional->adminRights[user] = admin.vadmin_rights; + if (admin.is_can_edit()) { + additional->adminCanEdit.emplace(user); + } else { + additional->adminCanEdit.erase(user); + } + if (auto promoted = App::userLoaded(admin.vpromoted_by.v)) { + auto it = additional->adminPromotedBy.find(user); + if (it == additional->adminPromotedBy.end()) { + additional->adminPromotedBy.emplace(user, promoted); + } else { + it->second = promoted; + } + } else { + LOG(("API Error: No user %1 for admin promoted by.").arg(admin.vpromoted_by.v)); + } + callback(user); + } + } break; + case mtpc_channelParticipantCreator: { + auto &creator = participant.c_channelParticipantCreator(); + if (auto user = App::userLoaded(creator.vuser_id.v)) { + additional->infoNotLoaded.erase(user); + additional->creator = user; + callback(user); + } + } break; + case mtpc_channelParticipantBanned: { + auto &banned = participant.c_channelParticipantBanned(); + if (auto user = App::userLoaded(banned.vuser_id.v)) { + additional->infoNotLoaded.erase(user); + additional->adminRights.erase(user); + additional->adminCanEdit.erase(user); + additional->adminPromotedBy.erase(user); + if (banned.is_left()) { + additional->kicked.emplace(user); + } else { + additional->kicked.erase(user); + } + additional->restrictedRights[user] = banned.vbanned_rights; + if (auto kickedby = App::userLoaded(banned.vkicked_by.v)) { + auto it = additional->restrictedBy.find(user); + if (it == additional->restrictedBy.end()) { + additional->restrictedBy.emplace(user, kickedby); + } else { + it->second = kickedby; + } + } + callback(user); + } + } break; + case mtpc_channelParticipant: { + auto &data = participant.c_channelParticipant(); + if (auto user = App::userLoaded(data.vuser_id.v)) { + additional->infoNotLoaded.erase(user); + additional->adminRights.erase(user); + additional->adminCanEdit.erase(user); + additional->adminPromotedBy.erase(user); + additional->restrictedRights.erase(user); + additional->kicked.erase(user); + additional->restrictedBy.erase(user); + callback(user); + } + } break; + default: Unexpected("Participant type in AddSpecialBoxController::HandleParticipant()"); + } +} + +AddSpecialBoxSearchController::AddSpecialBoxSearchController( + not_null peer, + not_null additional) +: _peer(peer) +, _additional(additional) +, _timer([=] { searchOnServer(); }) { +} + +void AddSpecialBoxSearchController::searchQuery(const QString &query) { + if (_query != query) { + _query = query; + _offset = 0; + _requestId = 0; + _participantsLoaded = false; + _chatsContactsAdded = false; + _globalLoaded = false; + if (!_query.isEmpty() && !searchParticipantsInCache()) { + _timer.callOnce(AutoSearchTimeout); + } else { + _timer.cancel(); + } + } +} + +void AddSpecialBoxSearchController::searchOnServer() { + Expects(!_query.isEmpty()); + + loadMoreRows(); +} + +bool AddSpecialBoxSearchController::isLoading() { + return _timer.isActive() || _requestId; +} + +bool AddSpecialBoxSearchController::searchParticipantsInCache() { + auto it = _participantsCache.find(_query); + if (it != _participantsCache.cend()) { + _requestId = 0; + searchParticipantsDone(_requestId, it->second.result, it->second.requestedCount); + return true; + } + return false; +} + +bool AddSpecialBoxSearchController::searchGlobalInCache() { + auto it = _globalCache.find(_query); + if (it != _globalCache.cend()) { + _requestId = 0; + searchGlobalDone(_requestId, it->second); + return true; + } + return false; +} + +bool AddSpecialBoxSearchController::loadMoreRows() { + if (_query.isEmpty()) { + return false; + } + if (_globalLoaded) { + return true; + } + if (_participantsLoaded) { + if (!_chatsContactsAdded) { + addChatsContacts(); + } + if (!isLoading() && !searchGlobalInCache()) { + requestGlobal(); + } + } else if (const auto chat = _peer->asChat()) { + if (chat->participants.empty()) { + return true; + } else { + addChatMembers(); + _participantsLoaded = true; + } + } else if (!isLoading()) { + requestParticipants(); + } + return true; +} + +void AddSpecialBoxSearchController::requestParticipants() { + Expects(_peer->isChannel()); + + // 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). + const auto perPage = kParticipantsPerPage; + const auto participantsHash = 0; + const auto channel = _peer->asChannel(); + + _requestId = request(MTPchannels_GetParticipants( + channel->inputChannel, + MTP_channelParticipantsSearch(MTP_string(_query)), + MTP_int(_offset), + MTP_int(perPage), + MTP_int(participantsHash) + )).done([=]( + const MTPchannels_ChannelParticipants &result, + mtpRequestId requestId) { + searchParticipantsDone(requestId, result, perPage); + }).fail([=](const RPCError &error, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + _participantsLoaded = true; + loadMoreRows(); + delegate()->peerListSearchRefreshRows(); + } + }).send(); + + auto entry = Query(); + entry.text = _query; + entry.offset = _offset; + _participantsQueries.emplace(_requestId, entry); +} + +void AddSpecialBoxSearchController::searchParticipantsDone( + mtpRequestId requestId, + const MTPchannels_ChannelParticipants &result, + int requestedCount) { + Expects(_peer->isChannel()); + + const auto channel = _peer->asChannel(); + auto query = _query; + if (requestId) { + Auth().api().parseChannelParticipants(channel, result, [&](auto&&...) { + auto it = _participantsQueries.find(requestId); + if (it != _participantsQueries.cend()) { + query = it->second.text; + if (it->second.offset == 0) { + auto &entry = _participantsCache[query]; + entry.result = result; + entry.requestedCount = requestedCount; + } + _participantsQueries.erase(it); + } + }); + } + + if (_requestId != requestId) { + return; + } + _requestId = 0; + TLHelp::VisitChannelParticipants(result, base::overload([&]( + const MTPDchannels_channelParticipants &data) { + auto &list = data.vparticipants.v; + if (list.size() < requestedCount) { + // We want cache to have full information about a query with small + // results count (if we don't need the second request). So we don't + // wait for an empty results list unlike the non-search peer list. + _participantsLoaded = true; + if (list.empty() && _offset == 0) { + // No results, so we want to request global search immediately. + loadMoreRows(); + } + } + auto addUser = [&](auto user) { + delegate()->peerListSearchAddRow(user); + }; + for (auto &participant : list) { + AddSpecialBoxController::HandleParticipant( + participant, + _additional, + addUser); + } + _offset += list.size(); + }, [&](mtpTypeId type) { + _participantsLoaded = true; + })); + + delegate()->peerListSearchRefreshRows(); +} + +void AddSpecialBoxSearchController::requestGlobal() { + if (_query.isEmpty()) { + _globalLoaded = true; + return; + } + + auto perPage = SearchPeopleLimit; + _requestId = request(MTPcontacts_Search( + MTP_string(_query), + MTP_int(perPage) + )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { + searchGlobalDone(requestId, result); + }).fail([=](const RPCError &error, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + _globalLoaded = true; + delegate()->peerListSearchRefreshRows(); + } + }).send(); + _globalQueries.emplace(_requestId, _query); +} + +void AddSpecialBoxSearchController::searchGlobalDone(mtpRequestId requestId, const MTPcontacts_Found &result) { + Expects(result.type() == mtpc_contacts_found); + + auto &found = result.c_contacts_found(); + auto query = _query; + if (requestId) { + App::feedUsers(found.vusers); + App::feedChats(found.vchats); + auto it = _globalQueries.find(requestId); + if (it != _globalQueries.cend()) { + query = it->second; + _globalCache[query] = result; + _globalQueries.erase(it); + } + } + + const auto feedList = [&](const MTPVector &list) { + const auto contains = [](const auto &map, const auto &value) { + return map.find(value) != map.end(); + }; + for (const auto &mtpPeer : list.v) { + const auto peerId = peerFromMTP(mtpPeer); + if (const auto peer = App::peerLoaded(peerId)) { + if (const auto user = peer->asUser()) { + if (_additional->creator != user + && !contains(_additional->adminRights, user) + && !contains(_additional->restrictedRights, user) + && !contains(_additional->external, user) + && !contains(_additional->kicked, user)) { + _additional->infoNotLoaded.emplace(user); + } + delegate()->peerListSearchAddRow(user); + } + } + } + }; + if (_requestId == requestId) { + _requestId = 0; + _globalLoaded = true; + feedList(found.vmy_results); + feedList(found.vresults); + delegate()->peerListSearchRefreshRows(); + } +} + +void AddSpecialBoxSearchController::addChatMembers() { + // #TODO groups +} + +void AddSpecialBoxSearchController::addChatsContacts() { + _chatsContactsAdded = true; + + auto wordList = TextUtilities::PrepareSearchWords(_query); + if (wordList.empty()) { + return; + } + + auto getSmallestIndex = [&](Dialogs::IndexedList *list) -> const Dialogs::List* { + if (list->isEmpty()) { + return nullptr; + } + + auto result = (const Dialogs::List*)nullptr; + for_const (auto &word, wordList) { + auto found = list->filtered(word[0]); + if (found->isEmpty()) { + return nullptr; + } + if (!result || result->size() > found->size()) { + result = found; + } + } + return result; + }; + auto dialogsIndex = getSmallestIndex(App::main()->dialogsList()); + auto contactsIndex = getSmallestIndex(App::main()->contactsNoDialogsList()); + + auto allWordsAreFound = [&](const base::flat_set &nameWords) { + auto hasNamePartStartingWith = [&](const QString &word) { + for (auto &nameWord : nameWords) { + if (nameWord.startsWith(word)) { + return true; + } + } + return false; + }; + + for_const (auto &word, wordList) { + if (!hasNamePartStartingWith(word)) { + return false; + } + } + return true; + }; + auto filterAndAppend = [&](const Dialogs::List *list) { + if (!list) { + return; + } + + for (const auto row : *list) { + if (const auto history = row->history()) { + if (const auto user = history->peer->asUser()) { + if (allWordsAreFound(user->nameWords())) { + delegate()->peerListSearchAddRow(user); + } + } + } + } + }; + filterAndAppend(dialogsIndex); + filterAndAppend(contactsIndex); + delegate()->peerListSearchRefreshRows(); +} diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.h b/Telegram/SourceFiles/boxes/peers/add_participants_box.h new file mode 100644 index 000000000..5a964a6df --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.h @@ -0,0 +1,183 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "boxes/peer_list_controllers.h" +#include "boxes/peers/edit_participants_box.h" + +class AddParticipantsBoxController : public ContactsBoxController { +public: + static void Start(not_null chat); + static void Start(not_null channel); + static void Start( + not_null channel, + base::flat_set> &&alreadyIn); + + AddParticipantsBoxController(PeerData *peer); + AddParticipantsBoxController( + not_null channel, + base::flat_set> &&alreadyIn); + + using ContactsBoxController::ContactsBoxController; + + void rowClicked(not_null row) override; + void itemDeselectedHook(not_null peer) override; + +protected: + void prepareViewHook() override; + std::unique_ptr createRow( + not_null user) override; + +private: + static void Start( + not_null channel, + base::flat_set> &&alreadyIn, + bool justCreated); + + int alreadyInCount() const; + bool isAlreadyIn(not_null user) const; + int fullCount() const; + void updateTitle(); + bool inviteSelectedUsers(not_null chat) const; + + PeerData *_peer = nullptr; + base::flat_set> _alreadyIn; + +}; + +// Adding an admin, banned or restricted user from channel members +// with search + contacts search + global search. +class AddSpecialBoxController + : public PeerListController + , private base::Subscriber + , private MTP::Sender + , public base::has_weak_ptr { +public: + using Role = ParticipantsBoxController::Role; + using Additional = ParticipantsBoxController::Additional; + + using AdminDoneCallback = Fn user, + const MTPChatAdminRights &adminRights)>; + using BannedDoneCallback = Fn user, + const MTPChatBannedRights &bannedRights)>; + AddSpecialBoxController( + not_null peer, + Role role, + AdminDoneCallback adminDoneCallback, + BannedDoneCallback bannedDoneCallback); + + void prepare() override; + void rowClicked(not_null row) override; + void loadMoreRows() override; + + std::unique_ptr createSearchRow( + not_null peer) override; + + // Callback(not_null) + template + static void HandleParticipant( + const MTPChannelParticipant &participant, + not_null additional, + Callback callback); + +private: + template + bool checkInfoLoaded(not_null user, Callback callback); + + void prepareChatRows(not_null chat); + void rebuildChatRows(not_null chat); + + void showAdmin(not_null user, bool sure = false); + void editAdminDone( + not_null user, + const MTPChatAdminRights &rights); + void showRestricted(not_null user, bool sure = false); + void editRestrictedDone( + not_null user, + const MTPChatBannedRights &rights); + void kickUser(not_null user, bool sure = false); + void restrictUserSure( + not_null user, + const MTPChatBannedRights &oldRights, + const MTPChatBannedRights &newRights); + bool appendRow(not_null user); + bool prependRow(not_null user); + std::unique_ptr createRow(not_null user) const; + + not_null _peer; + Role _role = Role::Admins; + int _offset = 0; + mtpRequestId _loadRequestId = 0; + bool _allLoaded = false; + Additional _additional; + std::unique_ptr _onlineSorter; + QPointer _editBox; + AdminDoneCallback _adminDoneCallback; + BannedDoneCallback _bannedDoneCallback; + +}; + +// Finds chat/channel members, then contacts, then global search results. +class AddSpecialBoxSearchController + : public PeerListSearchController + , private MTP::Sender { +public: + using Role = ParticipantsBoxController::Role; + using Additional = ParticipantsBoxController::Additional; + + AddSpecialBoxSearchController( + not_null peer, + not_null additional); + + void searchQuery(const QString &query) override; + bool isLoading() override; + bool loadMoreRows() override; + +private: + struct CacheEntry { + MTPchannels_ChannelParticipants result; + int requestedCount = 0; + }; + struct Query { + QString text; + int offset = 0; + }; + + void searchOnServer(); + bool searchParticipantsInCache(); + void searchParticipantsDone( + mtpRequestId requestId, + const MTPchannels_ChannelParticipants &result, + int requestedCount); + bool searchGlobalInCache(); + void searchGlobalDone( + mtpRequestId requestId, + const MTPcontacts_Found &result); + void requestParticipants(); + void addChatMembers(); + void addChatsContacts(); + void requestGlobal(); + + not_null _peer; + not_null _additional; + + base::Timer _timer; + QString _query; + mtpRequestId _requestId = 0; + int _offset = 0; + bool _participantsLoaded = false; + bool _chatsContactsAdded = false; + bool _globalLoaded = false; + std::map _participantsCache; + std::map _participantsQueries; + std::map _globalCache; + std::map _globalQueries; + +}; diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index 53dfd6205..782889cd8 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_permissions_box.h" #include "data/data_peer_values.h" #include "data/data_channel.h" +#include "data/data_chat.h" #include "data/data_user.h" #include "styles/style_boxes.h" @@ -34,7 +35,7 @@ class EditParticipantBox::Inner : public Ui::RpWidget { public: Inner( QWidget *parent, - not_null channel, + not_null peer, not_null user, bool hasAdminRights); @@ -46,7 +47,7 @@ protected: void paintEvent(QPaintEvent *e) override; private: - not_null _channel; + not_null _peer; not_null _user; object_ptr _userPhoto; Text _userName; @@ -57,11 +58,11 @@ private: EditParticipantBox::Inner::Inner( QWidget *parent, - not_null channel, + not_null peer, not_null user, bool hasAdminRights) : RpWidget(parent) -, _channel(channel) +, _peer(peer) , _user(user) , _userPhoto( this, @@ -138,10 +139,10 @@ void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) { EditParticipantBox::EditParticipantBox( QWidget*, - not_null channel, + not_null peer, not_null user, bool hasAdminRights) -: _channel(channel) +: _peer(peer) , _user(user) , _hasAdminRights(hasAdminRights) { } @@ -149,7 +150,7 @@ EditParticipantBox::EditParticipantBox( void EditParticipantBox::prepare() { _inner = setInnerWidget(object_ptr( this, - _channel, + _peer, _user, hasAdminRights())); setDimensionsToContent(st::boxWideWidth, _inner); @@ -166,19 +167,21 @@ Widget *EditParticipantBox::addControl( EditAdminBox::EditAdminBox( QWidget*, - not_null channel, + not_null peer, not_null user, const MTPChatAdminRights &rights) : EditParticipantBox( nullptr, - channel, + peer, user, (rights.c_chatAdminRights().vflags.v != 0)) , _oldRights(rights) { } -MTPChatAdminRights EditAdminBox::Defaults(not_null channel) { - const auto defaultRights = channel->isMegagroup() +MTPChatAdminRights EditAdminBox::Defaults(not_null peer) { + const auto defaultRights = peer->isChat() + ? ChatData::DefaultAdminRights() + : peer->isMegagroup() ? (Flag::f_change_info | Flag::f_delete_messages | Flag::f_ban_users @@ -206,26 +209,32 @@ void EditAdminBox::prepare() { object_ptr(this), st::rightsDividerMargin); - const auto prepareRights = hadRights ? _oldRights : Defaults(channel()); + const auto chat = peer()->asChat(); + const auto channel = peer()->asChannel(); + const auto prepareRights = hadRights ? _oldRights : Defaults(peer()); const auto filterByMyRights = canSave() && !hadRights - && !channel()->amCreator(); + && channel + && !channel->amCreator(); const auto prepareFlags = prepareRights.c_chatAdminRights().vflags.v - & (filterByMyRights ? channel()->adminRights() : ~Flag(0)); + & (filterByMyRights ? channel->adminRights() : ~Flag(0)); const auto disabledFlags = canSave() - ? (channel()->amCreator() + ? ((!channel || channel->amCreator()) ? Flags(0) - : ~channel()->adminRights()) + : ~channel->adminRights()) : ~Flags(0); + const auto anyoneCanAddMembers = chat + ? chat->anyoneCanAddMembers() + : channel->anyoneCanAddMembers(); auto [checkboxes, getChecked, changes] = CreateEditAdminRights( this, lng_rights_edit_admin_header, prepareFlags, disabledFlags, - channel()->isMegagroup(), - channel()->anyoneCanAddMembers()); + peer()->isChat() || peer()->isMegagroup(), + anyoneCanAddMembers); addControl(std::move(checkboxes), QMargins()); _aboutAddAdmins = addControl( @@ -248,9 +257,9 @@ void EditAdminBox::prepare() { return; } const auto newFlags = value() - & (channel()->amCreator() + & ((!channel || channel->amCreator()) ? ~Flags(0) - : channel()->adminRights()); + : channel->adminRights()); _saveCallback( _oldRights, MTP_chatAdminRights(MTP_flags(newFlags))); @@ -274,11 +283,11 @@ void EditAdminBox::refreshAboutAddAdminsText(bool canAddAdmins) { EditRestrictedBox::EditRestrictedBox( QWidget*, - not_null channel, + not_null peer, not_null user, bool hasAdminRights, const MTPChatBannedRights &rights) -: EditParticipantBox(nullptr, channel, user, hasAdminRights) +: EditParticipantBox(nullptr, peer, user, hasAdminRights) , _oldRights(rights) { } @@ -291,19 +300,24 @@ void EditRestrictedBox::prepare() { object_ptr(this), st::rightsDividerMargin); + const auto chat = peer()->asChat(); + const auto channel = peer()->asChannel(); + const auto defaultRestrictions = chat + ? chat->defaultRestrictions() + : channel->defaultRestrictions(); const auto prepareRights = (_oldRights.c_chatBannedRights().vflags.v ? _oldRights - : Defaults(channel())); + : Defaults(peer())); const auto prepareFlags = prepareRights.c_chatBannedRights().vflags.v - | (channel()->defaultRestrictions() - | (channel()->isPublic() - ? (Flag::f_change_info | Flag::f_pin_messages) - : Flags(0))); + | defaultRestrictions + | ((channel && channel->isPublic()) + ? (Flag::f_change_info | Flag::f_pin_messages) + : Flags(0)); const auto disabledFlags = canSave() - ? (channel()->defaultRestrictions() - | (channel()->isPublic() + ? (defaultRestrictions + | ((channel && channel->isPublic()) ? (Flag::f_change_info | Flag::f_pin_messages) - : Flags(0))) // #TODO groups + : Flags(0))) : ~Flags(0); auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions( @@ -348,8 +362,7 @@ void EditRestrictedBox::prepare() { } } -MTPChatBannedRights EditRestrictedBox::Defaults( - not_null channel) { +MTPChatBannedRights EditRestrictedBox::Defaults(not_null peer) { return MTP_chatBannedRights(MTP_flags(0), MTP_int(0)); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h index b3235a788..34142039a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h @@ -24,7 +24,7 @@ class EditParticipantBox : public BoxContent { public: EditParticipantBox( QWidget*, - not_null channel, + not_null peer, not_null user, bool hasAdminRights); @@ -34,8 +34,8 @@ protected: not_null user() const { return _user; } - not_null channel() const { - return _channel; + not_null peer() const { + return _peer; } template @@ -46,7 +46,7 @@ protected: } private: - not_null _channel; + not_null _peer; not_null _user; bool _hasAdminRights = false; @@ -59,7 +59,7 @@ class EditAdminBox : public EditParticipantBox { public: EditAdminBox( QWidget*, - not_null channel, + not_null peer, not_null user, const MTPChatAdminRights &rights); @@ -75,7 +75,7 @@ private: using Flag = MTPDchatAdminRights::Flag; using Flags = MTPDchatAdminRights::Flags; - static MTPChatAdminRights Defaults(not_null channel); + static MTPChatAdminRights Defaults(not_null peer); bool canSave() const { return !!_saveCallback; @@ -96,7 +96,7 @@ class EditRestrictedBox : public EditParticipantBox { public: EditRestrictedBox( QWidget*, - not_null channel, + not_null peer, not_null user, bool hasAdminRights, const MTPChatBannedRights &rights); @@ -113,7 +113,7 @@ private: using Flag = MTPDchatBannedRights::Flag; using Flags = MTPDchatBannedRights::Flags; - static MTPChatBannedRights Defaults(not_null channel); + static MTPChatBannedRights Defaults(not_null peer); bool canSave() const { return !!_saveCallback; @@ -121,7 +121,6 @@ private: void showRestrictUntil(); void setRestrictUntil(TimeId until); bool isUntilForever() const; - void clearVariants(); void createUntilGroup(); void createUntilVariants(); TimeId getRealUntilValue() const; diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp new file mode 100644 index 000000000..899ba4392 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -0,0 +1,1508 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/peers/edit_participants_box.h" + +#include "boxes/peer_list_controllers.h" +#include "boxes/peers/edit_participant_box.h" +#include "boxes/peers/add_participants_box.h" +#include "boxes/confirm_box.h" +#include "boxes/add_contact_box.h" +#include "core/tl_help.h" +#include "base/overload.h" +#include "auth_session.h" +#include "apiwrap.h" +#include "lang/lang_keys.h" +#include "mainwidget.h" +#include "observer_peer.h" +#include "dialogs/dialogs_indexed_list.h" +#include "data/data_peer_values.h" +#include "data/data_session.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_user.h" +#include "ui/widgets/popup_menu.h" +#include "window/window_controller.h" +#include "history/history.h" + +namespace { + +constexpr auto kParticipantsFirstPageCount = 16; +constexpr auto kParticipantsPerPage = 200; +constexpr auto kSortByOnlineDelay = TimeMs(1000); + +void RemoveAdmin( + not_null channel, + not_null user, + const MTPChatAdminRights &oldRights, + Fn onDone) { + const auto newRights = MTP_chatAdminRights(MTP_flags(0)); + channel->session().api().request(MTPchannels_EditAdmin( + channel->inputChannel, + user->inputUser, + newRights + )).done([=](const MTPUpdates &result) { + channel->session().api().applyUpdates(result); + channel->applyEditAdmin(user, oldRights, newRights); + onDone(); + }).send(); +} + +void EditChatAdmin( + not_null chat, + not_null user, + bool isAdmin, + Fn onDone) { + chat->session().api().request(MTPmessages_EditChatAdmin( + chat->inputChat, + user->inputUser, + MTP_bool(isAdmin) + )).done([=](const MTPBool &result) { + chat->applyEditAdmin(user, isAdmin); + onDone(); + }).send(); +} + +} // namespace + +Fn SaveAdminCallback( + not_null channel, + not_null user, + Fn onDone, + Fn onFail) { + return [=]( + const MTPChatAdminRights &oldRights, + const MTPChatAdminRights &newRights) { + auto done = [=](const MTPUpdates &result) { + Auth().api().applyUpdates(result); + channel->applyEditAdmin(user, oldRights, newRights); + onDone(newRights); + }; + auto fail = [=](const RPCError &error) { + if (MTP::isDefaultHandledError(error)) { + return false; + } + if (error.type() == qstr("USER_NOT_MUTUAL_CONTACT")) { + Ui::show( + Box(PeerFloodErrorText( + channel->isMegagroup() + ? PeerFloodType::InviteGroup + : PeerFloodType::InviteChannel)), + LayerOption::KeepOther); + } else if (error.type() == qstr("BOT_GROUPS_BLOCKED")) { + Ui::show( + Box(lang(lng_error_cant_add_bot)), + LayerOption::KeepOther); + } else if (error.type() == qstr("ADMINS_TOO_MUCH")) { + Ui::show( + Box(lang(channel->isMegagroup() + ? lng_error_admin_limit + : lng_error_admin_limit_channel)), + LayerOption::KeepOther); + } + onFail(); + return true; + }; + MTP::send( + MTPchannels_EditAdmin( + channel->inputChannel, + user->inputUser, + newRights), + rpcDone(std::move(done)), + rpcFail(std::move(fail))); + }; +} + +Fn SaveRestrictedCallback( + not_null channel, + not_null user, + Fn onDone, + Fn onFail) { + return [=]( + const MTPChatBannedRights &oldRights, + const MTPChatBannedRights &newRights) { + auto done = [=](const MTPUpdates &result) { + Auth().api().applyUpdates(result); + channel->applyEditBanned(user, oldRights, newRights); + onDone(newRights); + }; + auto fail = [=](const RPCError &error) { + if (MTP::isDefaultHandledError(error)) { + return false; + } + onFail(); + return true; + }; + MTP::send( + MTPchannels_EditBanned( + channel->inputChannel, + user->inputUser, + newRights), + rpcDone(std::move(done)), + rpcFail(std::move(fail))); + }; +} + +void ParticipantsBoxController::Additional::fillCreator( + not_null peer) { + if (const auto chat = peer->asChat()) { + creator = chat->owner().userLoaded(chat->creator); + } else if (const auto channel = peer->asChannel()) { + if (channel->mgInfo) { + creator = channel->mgInfo->creator; + } + } +} + +ParticipantsOnlineSorter::ParticipantsOnlineSorter( + not_null peer, + not_null delegate) +: _peer(peer) +, _delegate(delegate) +, _sortByOnlineTimer([=] { sort(); }) { + const auto handleUpdate = [=](const Notify::PeerUpdate &update) { + const auto peerId = update.peer->id; + if (const auto row = _delegate->peerListFindRow(peerId)) { + row->refreshStatus(); + sortDelayed(); + } + }; + + subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler( + Notify::PeerUpdate::Flag::UserOnlineChanged, + handleUpdate)); + sort(); +} + +void ParticipantsOnlineSorter::sortDelayed() { + if (!_sortByOnlineTimer.isActive()) { + _sortByOnlineTimer.callOnce(kSortByOnlineDelay); + } +} + +void ParticipantsOnlineSorter::sort() { + const auto channel = _peer->asChannel(); + if (channel + && (!channel->isMegagroup() + || channel->membersCount() > Global::ChatSizeMax())) { + _onlineCount = 0; + return; + } + const auto now = unixtime(); + _delegate->peerListSortRows([&]( + const PeerListRow &a, + const PeerListRow &b) { + return Data::SortByOnlineValue(a.peer()->asUser(), now) > + Data::SortByOnlineValue(b.peer()->asUser(), now); + }); + refreshOnlineCount(); +} + +rpl::producer ParticipantsOnlineSorter::onlineCountValue() const { + return _onlineCount.value(); +} + +void ParticipantsOnlineSorter::refreshOnlineCount() { + const auto now = unixtime(); + auto left = 0, right = _delegate->peerListFullRowsCount(); + while (right > left) { + const auto middle = (left + right) / 2; + const auto row = _delegate->peerListRowAt(middle); + if (Data::OnlineTextActive(row->peer()->asUser(), now)) { + left = middle + 1; + } else { + right = middle; + } + } + _onlineCount = left; +} + +ParticipantsBoxController::ParticipantsBoxController( + not_null navigation, + not_null peer, + Role role) +: PeerListController(CreateSearchController(peer, role, &_additional)) +, _navigation(navigation) +, _peer(peer) +, _role(role) { + _additional.fillCreator(_peer); + if (_role == Role::Profile) { + setupListChangeViewers(); + } +} + +void ParticipantsBoxController::setupListChangeViewers() { + const auto channel = _peer->asChannel(); + if (!channel || !channel->isMegagroup()) { + return; + } + + Auth().data().megagroupParticipantAdded( + channel + ) | rpl::start_with_next([=](not_null user) { + if (delegate()->peerListFullRowsCount() > 0) { + if (delegate()->peerListRowAt(0)->peer() == user) { + return; + } + } + if (const auto row = delegate()->peerListFindRow(user->id)) { + delegate()->peerListPartitionRows([&](const PeerListRow &row) { + return (row.peer() == user); + }); + } else { + delegate()->peerListPrependRow(createRow(user)); + delegate()->peerListRefreshRows(); + if (_onlineSorter) { + _onlineSorter->sort(); + } + } + }, lifetime()); + + Auth().data().megagroupParticipantRemoved( + channel + ) | rpl::start_with_next([=](not_null user) { + if (const auto row = delegate()->peerListFindRow(user->id)) { + delegate()->peerListRemoveRow(row); + } + delegate()->peerListRefreshRows(); + }, lifetime()); +} + +auto ParticipantsBoxController::CreateSearchController( + not_null peer, + Role role, + not_null additional) +-> std::unique_ptr { + const auto channel = peer->asChannel(); + + // In admins box complex search is used for adding new admins. + if (channel && (role != Role::Admins || channel->canAddAdmins())) { + return std::make_unique( + channel, + role, + additional); + } + return nullptr; +} + +void ParticipantsBoxController::Start( + not_null navigation, + not_null peer, + Role role) { + auto controller = std::make_unique( + navigation, + peer, + role); + auto initBox = [=, controller = controller.get()]( + not_null box) { + box->addButton(langFactory(lng_close), [=] { box->closeBox(); }); + + const auto chat = peer->asChat(); + const auto channel = peer->asChannel(); + const auto canAddNewItem = [&] { + Assert(chat != nullptr || channel != nullptr); + + switch (role) { + case Role::Members: + return chat + ? chat->canAddMembers() + : (channel->canAddMembers() + && (channel->membersCount() < Global::ChatSizeMax() + || channel->isMegagroup())); + case Role::Admins: + return chat + ? chat->canAddAdmins() + : channel->canAddAdmins(); + case Role::Restricted: + case Role::Kicked: + return chat + ? chat->canBanMembers() + : channel->canBanMembers(); + } + + Unexpected("Role value in ParticipantsBoxController::Start()"); + }(); + const auto addNewItemText = [&] { + switch (role) { + case Role::Members: + return langFactory((chat || channel->isMegagroup()) + ? lng_channel_add_members + : lng_channel_add_users); + case Role::Admins: + return langFactory(lng_channel_add_admin); + case Role::Restricted: + return langFactory(lng_channel_add_restricted); + case Role::Kicked: + return langFactory(lng_channel_add_banned); + } + Unexpected("Role value in ParticipantsBoxController::Start()"); + }(); + if (canAddNewItem) { + box->addLeftButton(addNewItemText, [=] { + controller->addNewItem(); + }); + } + }; + Ui::show( + Box(std::move(controller), initBox), + LayerOption::KeepOther); +} + +void ParticipantsBoxController::addNewItem() { + Expects(_role != Role::Profile); + + if (_role == Role::Members) { + addNewParticipants(); + return; + } + auto weak = base::make_weak(this); + _addBox = Ui::show( + Box(std::make_unique( + _peer, + _role, + [=](not_null user, const MTPChatAdminRights &rights) { + if (const auto strong = weak.get()) { + strong->editAdminDone(user, rights); + } + }, [=](not_null user, const MTPChatBannedRights &rights) { + if (const auto strong = weak.get()) { + strong->editRestrictedDone(user, rights); + } + }), [](not_null box) { + box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); + }), + LayerOption::KeepOther); +} + +void ParticipantsBoxController::addNewParticipants() { + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + if (chat) { + AddParticipantsBoxController::Start(chat); + } else if (channel->isMegagroup() + || channel->membersCount() < Global::ChatSizeMax()) { + const auto count = delegate()->peerListFullRowsCount(); + auto already = std::vector>(); + already.reserve(count); + for (auto i = 0; i != count; ++i) { + already.push_back( + delegate()->peerListRowAt(i)->peer()->asUser()); + } + AddParticipantsBoxController::Start( + channel, + { already.begin(), already.end() }); + } else { + Ui::show(Box(channel), LayerOption::KeepOther); + } +} + +void ParticipantsBoxController::peerListSearchAddRow(not_null peer) { + PeerListController::peerListSearchAddRow(peer); + if (_role == Role::Restricted && delegate()->peerListFullRowsCount() > 0) { + setDescriptionText(QString()); + } +} + +std::unique_ptr ParticipantsBoxController::createSearchRow( + not_null peer) { + if (auto user = peer->asUser()) { + return createRow(user); + } + return nullptr; +} + +std::unique_ptr ParticipantsBoxController::createRestoredRow( + not_null peer) { + if (auto user = peer->asUser()) { + return createRow(user); + } + return nullptr; +} + +std::unique_ptr ParticipantsBoxController::saveState() const { + Expects(_role == Role::Profile); + + auto result = PeerListController::saveState(); + + auto my = std::make_unique(); + my->additional = _additional; + my->offset = _offset; + my->allLoaded = _allLoaded; + my->wasLoading = (_loadRequestId != 0); + if (auto search = searchController()) { + my->searchState = search->saveState(); + } + + if (_peer->isMegagroup()) { + const auto channel = _peer->asChannel(); + + auto weak = result.get(); + Auth().data().megagroupParticipantAdded( + channel + ) | rpl::start_with_next([weak](not_null user) { + if (!weak->list.empty()) { + if (weak->list[0] == user) { + return; + } + } + auto pos = ranges::find(weak->list, user); + if (pos == weak->list.cend()) { + weak->list.push_back(user); + } + ranges::stable_partition( + weak->list, + [user](auto peer) { return (peer == user); }); + }, my->lifetime); + Auth().data().megagroupParticipantRemoved( + channel + ) | rpl::start_with_next([weak](not_null user) { + weak->list.erase(std::remove( + weak->list.begin(), + weak->list.end(), + user), weak->list.end()); + weak->filterResults.erase(std::remove( + weak->filterResults.begin(), + weak->filterResults.end(), + user), weak->filterResults.end()); + }, my->lifetime); + } + result->controllerState = std::move(my); + return result; +} + +void ParticipantsBoxController::restoreState( + std::unique_ptr state) { + auto typeErasedState = state + ? state->controllerState.get() + : nullptr; + if (auto my = dynamic_cast(typeErasedState)) { + if (auto requestId = base::take(_loadRequestId)) { + request(requestId).cancel(); + } + + _additional = std::move(my->additional); + _offset = my->offset; + _allLoaded = my->allLoaded; + if (auto search = searchController()) { + search->restoreState(std::move(my->searchState)); + } + if (my->wasLoading) { + loadMoreRows(); + } + PeerListController::restoreState(std::move(state)); + if (delegate()->peerListFullRowsCount() > 0) { + setNonEmptyDescription(); + } else if (_allLoaded) { + setDescriptionText(lang(lng_blocked_list_not_found)); + } + if (_onlineSorter) { + _onlineSorter->sort(); + } + } +} + +rpl::producer ParticipantsBoxController::onlineCountValue() const { + return _onlineSorter + ? _onlineSorter->onlineCountValue() + : rpl::single(0); +} + +template +void ParticipantsBoxController::HandleParticipant(const MTPChannelParticipant &participant, Role role, not_null additional, Callback callback) { + if ((role == Role::Profile + || 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; + if (admin.is_can_edit()) { + additional->adminCanEdit.emplace(user); + } else { + additional->adminCanEdit.erase(user); + } + if (auto promoted = App::userLoaded(admin.vpromoted_by.v)) { + auto it = additional->adminPromotedBy.find(user); + if (it == additional->adminPromotedBy.end()) { + additional->adminPromotedBy.emplace(user, promoted); + } else { + it->second = promoted; + } + } else { + LOG(("API Error: No user %1 for admin promoted by.").arg(admin.vpromoted_by.v)); + } + callback(user); + } + } else if ((role == Role::Profile + || 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::Profile + || 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; + if (auto kickedby = App::userLoaded(banned.vkicked_by.v)) { + auto it = additional->restrictedBy.find(user); + if (it == additional->restrictedBy.end()) { + additional->restrictedBy.emplace(user, kickedby); + } else { + it->second = kickedby; + } + } + callback(user); + } + } else if ((role == Role::Profile + || 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::Profile + || 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())); + } +} + +void ParticipantsBoxController::prepare() { + const auto titleKey = [&] { + switch (_role) { + case Role::Admins: return lng_channel_admins; + case Role::Profile: + case Role::Members: return lng_profile_participants_section; + case Role::Restricted: return lng_restricted_list_title; + case Role::Kicked: return lng_banned_list_title; + } + Unexpected("Role in ParticipantsBoxController::prepare()"); + }(); + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + delegate()->peerListSetTitle(langFactory(titleKey)); + setDescriptionText(lang(lng_contacts_loading)); + setSearchNoResultsText(lang(lng_blocked_list_not_found)); + + if (const auto chat = _peer->asChat()) { + prepareChatRows(chat); + } else { + loadMoreRows(); + } + if (_role == Role::Profile && !_onlineSorter) { + _onlineSorter = std::make_unique( + _peer, + delegate()); + } + delegate()->peerListRefreshRows(); +} + +void ParticipantsBoxController::prepareChatRows(not_null chat) { + _onlineSorter = std::make_unique( + chat, + delegate()); + + rebuildChatRows(chat); + if (!delegate()->peerListFullRowsCount()) { + chat->updateFullForced(); + } + + using UpdateFlag = Notify::PeerUpdate::Flag; + subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler( + UpdateFlag::MembersChanged + | UpdateFlag::AdminsChanged, + [=](const Notify::PeerUpdate &update) { + if (update.flags & UpdateFlag::MembersChanged) { + if (update.peer == chat) { + rebuildChatRows(chat); + } + } else if (update.flags & UpdateFlag::AdminsChanged) { + if (update.peer == chat) { + rebuildRowTypes(); + } + } + })); +} + +void ParticipantsBoxController::rebuildChatRows(not_null chat) { + if (chat->participants.empty()) { + // We get such updates often + // (when participants list was invalidated). + //while (delegate()->peerListFullRowsCount() > 0) { + // delegate()->peerListRemoveRow( + // delegate()->peerListRowAt(0)); + //} + return; + } + + auto &participants = chat->participants; + auto count = delegate()->peerListFullRowsCount(); + for (auto i = 0; i != count;) { + auto row = delegate()->peerListRowAt(i); + auto user = row->peer()->asUser(); + if (participants.contains(user)) { + ++i; + } else { + delegate()->peerListRemoveRow(row); + --count; + } + } + for (const auto [user, v] : participants) { + if (auto row = createRow(user)) { + delegate()->peerListAppendRow(std::move(row)); + } + } + _onlineSorter->sort(); + + delegate()->peerListRefreshRows(); +} + +void ParticipantsBoxController::rebuildRowTypes() { + const auto count = delegate()->peerListFullRowsCount(); + for (auto i = 0; i != count; ++i) { + const auto row = static_cast( + delegate()->peerListRowAt(i).get()); + row->setType(computeType(row->user())); + } + delegate()->peerListRefreshRows(); +} + +void ParticipantsBoxController::loadMoreRows() { + if (searchController() && searchController()->loadMoreRows()) { + return; + } else if (!_peer->isChannel() || _loadRequestId || _allLoaded) { + return; + } + + const auto channel = _peer->asChannel(); + if (feedMegagroupLastParticipants()) { + return; + } + + const auto filter = [&] { + if (_role == Role::Members || _role == Role::Profile) { + return MTP_channelParticipantsRecent(); + } else if (_role == Role::Admins) { + return MTP_channelParticipantsAdmins(); + } else if (_role == Role::Restricted) { + return MTP_channelParticipantsBanned(MTP_string(QString())); + } + return MTP_channelParticipantsKicked(MTP_string(QString())); + }(); + + // First query is small and fast, next loads a lot of rows. + const auto perPage = (_offset > 0) + ? kParticipantsPerPage + : kParticipantsFirstPageCount; + const auto participantsHash = 0; + + _loadRequestId = request(MTPchannels_GetParticipants( + channel->inputChannel, + filter, + MTP_int(_offset), + MTP_int(perPage), + MTP_int(participantsHash) + )).done([=](const MTPchannels_ChannelParticipants &result) { + const auto firstLoad = !_offset; + _loadRequestId = 0; + + auto wasRecentRequest = firstLoad + && (_role == Role::Members || _role == Role::Profile); + auto parseParticipants = [&](auto &&result, auto &&callback) { + if (wasRecentRequest) { + Auth().api().parseRecentChannelParticipants( + channel, + result, + callback); + } else { + Auth().api().parseChannelParticipants( + channel, + result, + callback); + } + }; + parseParticipants(result, [&]( + int availableCount, + const QVector &list) { + for (auto &participant : list) { + HandleParticipant( + participant, + _role, + &_additional, + [&](auto user) { appendRow(user); }); + } + if (auto size = list.size()) { + _offset += size; + } else { + // To be sure - wait for a whole empty result list. + _allLoaded = true; + } + }); + + if (delegate()->peerListFullRowsCount() > 0) { + if (_onlineSorter) { + _onlineSorter->sort(); + } + if (firstLoad) { + setNonEmptyDescription(); + } + } else if (_allLoaded) { + setDescriptionText(lang(lng_blocked_list_not_found)); + } + delegate()->peerListRefreshRows(); + }).fail([this](const RPCError &error) { + _loadRequestId = 0; + }).send(); +} + +void ParticipantsBoxController::setNonEmptyDescription() { + setDescriptionText((_role == Role::Kicked) + ? lang((_peer->isChat() || _peer->isMegagroup()) + ? lng_group_blocked_list_about + : lng_channel_blocked_list_about) + : QString()); +} + +bool ParticipantsBoxController::feedMegagroupLastParticipants() { + if ((_role != Role::Members && _role != Role::Profile) + || delegate()->peerListFullRowsCount() > 0) { + return false; + } + const auto megagroup = _peer->asMegagroup(); + if (!megagroup) { + return false; + } + auto info = megagroup->mgInfo.get(); + // + // channelFull and channels_channelParticipants members count is desynced + // so we almost always have LastParticipantsCountOutdated that is set + // inside setMembersCount() and so we almost never use lastParticipants. + // + // => disable this check temporarily. + // + //if (info->lastParticipantsStatus != MegagroupInfo::LastParticipantsUpToDate) { + // _channel->updateFull(); + // return false; + //} + if (info->lastParticipants.empty()) { + return false; + } + + if (info->creator) { + _additional.creator = info->creator; + } + for_const (auto user, info->lastParticipants) { + auto admin = info->lastAdmins.find(user); + if (admin != info->lastAdmins.cend()) { + _additional.restrictedRights.erase(user); + if (admin->second.canEdit) { + _additional.adminCanEdit.emplace(user); + } else { + _additional.adminCanEdit.erase(user); + } + _additional.adminRights.emplace(user, admin->second.rights); + } else { + _additional.adminCanEdit.erase(user); + _additional.adminRights.erase(user); + auto restricted = info->lastRestricted.find(user); + if (restricted != info->lastRestricted.cend()) { + _additional.restrictedRights.emplace(user, restricted->second.rights); + } else { + _additional.restrictedRights.erase(user); + } + } + appendRow(user); + + // + // Don't count lastParticipants in _offset, because we don't know + // their exact information (admin / creator / restricted), they + // could simply be added from the last messages authors. + // + //++_offset; + } + if (_onlineSorter) { + _onlineSorter->sort(); + } + return true; +} + +void ParticipantsBoxController::rowClicked(not_null row) { + auto user = row->peer()->asUser(); + Expects(user != nullptr); + + if (_role == Role::Admins) { + showAdmin(user); + } else if (_role == Role::Restricted || _role == Role::Kicked) { + showRestricted(user); + } else { + _navigation->showPeerInfo(row->peer()); + } +} + +void ParticipantsBoxController::rowActionClicked(not_null row) { + auto user = row->peer()->asUser(); + Expects(user != nullptr); + + if (_role == Role::Members || _role == Role::Profile) { + kickMember(user); + } else if (_role == Role::Admins) { + removeAdmin(user); + } else if (_role == Role::Restricted) { + showRestricted(user); + } else { + removeKicked(row, user); + } +} + +bool ParticipantsBoxController::canEditAdminByRights( + not_null user) const { + if (const auto chat = _peer->asChat()) { + return chat->amCreator() && (user != _additional.creator); + } + if (_additional.adminRights.find(user) + != _additional.adminRights.cend()) { + return (_additional.adminCanEdit.find(user) + != _additional.adminCanEdit.cend()); + } + return (user != _additional.creator); +} + +bool ParticipantsBoxController::canEditAdmin( + not_null user) const { + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + if (user->isSelf()) { + return false; + } else if (chat ? chat->amCreator() : channel->amCreator()) { + return true; + } else if (!canEditAdminByRights(user)) { + return false; + } + return (channel != nullptr) + && (channel->adminRights() & ChatAdminRight::f_add_admins); +} + +bool ParticipantsBoxController::canRestrictUser( + not_null user) const { + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + if (user->isSelf()) { + return false; + } else if (chat ? chat->amCreator() : channel->amCreator()) { + return true; + } else if (!canEditAdminByRights(user)) { + return false; + } + return (channel != nullptr) + && (channel->adminRights() & ChatAdminRight::f_ban_users); +} + +base::unique_qptr ParticipantsBoxController::rowContextMenu( + QWidget *parent, + not_null row) { + Expects(row->peer()->isUser()); + + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + const auto user = row->peer()->asUser(); + auto result = base::make_unique_q(parent); + result->addAction( + lang(lng_context_view_profile), + [weak = base::make_weak(this), user] { + if (const auto strong = weak.get()) { + strong->_navigation->showPeerInfo(user); + } + }); + if (canEditAdmin(user)) { + auto it = _additional.adminRights.find(user); + auto isCreator = (user == _additional.creator); + auto notAdmin = !isCreator + && (it == _additional.adminRights.cend()) + && (channel || !chat->admins.contains(user)); + auto label = lang(notAdmin + ? lng_context_promote_admin + : lng_context_edit_permissions); + result->addAction( + label, + [weak = base::make_weak(this), user] { + if (const auto strong = weak.get()) { + strong->showAdmin(user); + } + }); + } + if (canRestrictUser(user)) { + const auto isGroup = _peer->isChat() || _peer->isMegagroup(); + const auto weak = base::make_weak(this); + if (isGroup) { + result->addAction( + lang(lng_context_restrict_user), + [=]{ + if (const auto strong = weak.get()) { + strong->showRestricted(user); + } + }); + } + result->addAction( + lang(isGroup + ? lng_context_remove_from_group + : lng_profile_kick), + [=] { + if (const auto strong = weak.get()) { + strong->kickMember(user); + } + }); + } + return result; +} + +void ParticipantsBoxController::showAdmin(not_null user) { + const auto it = _additional.adminRights.find(user); + const auto isCreator = (user == _additional.creator); + const auto isAdmin = isCreator || (it != _additional.adminRights.cend()); + const auto currentRights = isCreator + ? MTP_chatAdminRights( + MTP_flags(~MTPDchatAdminRights::Flag::f_add_admins + | MTPDchatAdminRights::Flag::f_add_admins)) + : isAdmin + ? it->second + : MTP_chatAdminRights(MTP_flags(0)); + const auto weak = base::make_weak(this); + auto box = Box(_peer, user, currentRights); + const auto canEdit = (_additional.adminCanEdit.find(user) + != _additional.adminCanEdit.end()); + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + const auto canSave = isAdmin + ? canEdit + : chat + ? chat->canAddAdmins() + : channel->canAddAdmins(); + if (canSave) { + if (chat) { + // #TODO groups autoconv + } else if (channel) { + box->setSaveCallback(SaveAdminCallback(channel, user, [=]( + const MTPChatAdminRights &newRights) { + if (weak) { + weak->editAdminDone(user, newRights); + } + }, [=] { + if (weak && weak->_editBox) { + weak->_editBox->closeBox(); + } + })); + } + } + _editBox = Ui::show(std::move(box), LayerOption::KeepOther); +} + +void ParticipantsBoxController::editAdminDone( + not_null user, + const MTPChatAdminRights &rights) { + if (_editBox) { + _editBox->closeBox(); + } + if (_addBox) { + _addBox->closeBox(); + } + auto notAdmin = (rights.c_chatAdminRights().vflags.v == 0); + if (notAdmin) { + _additional.adminRights.erase(user); + _additional.adminPromotedBy.erase(user); + _additional.adminCanEdit.erase(user); + if (_role == Role::Admins) { + removeRow(user); + } + } else { + // It won't be replaced if the entry already exists. + _additional.adminPromotedBy.emplace(user, Auth().user()); + _additional.adminCanEdit.emplace(user); + _additional.adminRights[user] = rights; + _additional.kicked.erase(user); + _additional.restrictedRights.erase(user); + _additional.restrictedBy.erase(user); + if (_role == Role::Admins) { + prependRow(user); + } else if (_role == Role::Kicked || _role == Role::Restricted) { + removeRow(user); + } + } + recomputeTypeFor(user); + delegate()->peerListRefreshRows(); +} + +void ParticipantsBoxController::showRestricted(not_null user) { + const auto it = _additional.restrictedRights.find(user); + const auto currentRights = (it == _additional.restrictedRights.cend()) + ? MTP_chatBannedRights(MTP_flags(0), MTP_int(0)) + : it->second; + const auto weak = base::make_weak(this); + const auto hasAdminRights = false; + auto box = Box( + _peer, + user, + hasAdminRights, + currentRights); + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + const auto canSave = chat + ? chat->canBanMembers() + : channel->canBanMembers(); + if (canSave) { + if (chat) { + // #TODO groups autoconv + } else { + box->setSaveCallback(SaveRestrictedCallback(channel, user, [=]( + const MTPChatBannedRights &newRights) { + if (weak) { + weak->editRestrictedDone(user, newRights); + } + }, [=] { + if (weak && weak->_editBox) { + weak->_editBox->closeBox(); + } + })); + } + } + _editBox = Ui::show(std::move(box), LayerOption::KeepOther); +} + +void ParticipantsBoxController::editRestrictedDone( + not_null user, + const MTPChatBannedRights &rights) { + if (_editBox) { + _editBox->closeBox(); + } + if (_addBox) { + _addBox->closeBox(); + } + auto notBanned = (rights.c_chatBannedRights().vflags.v == 0); + auto fullBanned = rights.c_chatBannedRights().is_view_messages(); + if (notBanned) { + _additional.kicked.erase(user); + _additional.restrictedRights.erase(user); + _additional.restrictedBy.erase(user); + if (_role == Role::Kicked || _role == Role::Restricted) { + removeRow(user); + } + } else { + _additional.adminRights.erase(user); + _additional.adminCanEdit.erase(user); + _additional.adminPromotedBy.erase(user); + _additional.restrictedBy.emplace(user, Auth().user()); + if (fullBanned) { + _additional.kicked.emplace(user); + _additional.restrictedRights.erase(user); + if (_role == Role::Kicked) { + prependRow(user); + } else if (_role == Role::Admins + || _role == Role::Restricted + || _role == Role::Members) { + removeRow(user); + } + } else { + _additional.restrictedRights[user] = rights; + _additional.kicked.erase(user); + if (_role == Role::Restricted) { + prependRow(user); + } else if (_role == Role::Kicked + || _role == Role::Admins + || _role == Role::Members) { + removeRow(user); + } + } + } + recomputeTypeFor(user); + delegate()->peerListRefreshRows(); +} + +void ParticipantsBoxController::kickMember(not_null user) { + const auto text = ((_peer->isChat() || _peer->isMegagroup()) + ? lng_profile_sure_kick + : lng_profile_sure_kick_channel)(lt_user, user->firstName); + const auto weak = base::make_weak(this); + _editBox = Ui::show(Box(text, lang(lng_box_remove), [=] { + if (const auto strong = weak.get()) { + strong->kickMemberSure(user); + } + }), LayerOption::KeepOther); +} + +void ParticipantsBoxController::kickMemberSure(not_null user) { + if (_editBox) { + _editBox->closeBox(); + } + const auto i = _additional.restrictedRights.find(user); + const auto currentRights = (i == _additional.restrictedRights.cend()) + ? MTP_chatBannedRights(MTP_flags(0), MTP_int(0)) + : i->second; + + if (const auto row = delegate()->peerListFindRow(user->id)) { + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); + } + if (const auto chat = _peer->asChat()) { + Auth().api().kickParticipant(chat, user); + } else if (const auto channel = _peer->asChannel()) { + Auth().api().kickParticipant(channel, user, currentRights); + } +} + +void ParticipantsBoxController::removeAdmin(not_null user) { + const auto text = lng_profile_sure_remove_admin(lt_user, user->firstName); + const auto weak = base::make_weak(this); + _editBox = Ui::show(Box(text, lang(lng_box_remove), [=] { + if (const auto strong = weak.get()) { + strong->removeAdminSure(user); + } + }), LayerOption::KeepOther); +} + +void ParticipantsBoxController::removeAdminSure(not_null user) { + if (_editBox) { + _editBox->closeBox(); + } + const auto weak = base::make_weak(this); + if (const auto chat = _peer->asChat()) { + EditChatAdmin(chat, user, false, [=] { + if (const auto strong = weak.get()) { + const auto newRights = MTP_chatAdminRights(MTP_flags(0)); + strong->editAdminDone(user, newRights); + } + }); + } else if (const auto channel = _peer->asChannel()) { + const auto oldRightsIt = _additional.adminRights.find(user); + if (oldRightsIt == _additional.adminRights.cend()) { + return; + } + RemoveAdmin(channel, user, oldRightsIt->second, [=] { + if (const auto strong = weak.get()) { + const auto newRights = MTP_chatAdminRights(MTP_flags(0)); + strong->editAdminDone(user, newRights); + } + }); + } +} + +void ParticipantsBoxController::removeKicked( + not_null row, + not_null user) { + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); + + if (const auto channel = _peer->asChannel()) { + Auth().api().unblockParticipant(channel, user); + } +} + +bool ParticipantsBoxController::appendRow(not_null user) { + if (delegate()->peerListFindRow(user->id)) { + recomputeTypeFor(user); + return false; + } + delegate()->peerListAppendRow(createRow(user)); + if (_role != Role::Kicked) { + setDescriptionText(QString()); + } + return true; +} + +bool ParticipantsBoxController::prependRow(not_null user) { + if (auto row = delegate()->peerListFindRow(user->id)) { + recomputeTypeFor(user); + refreshCustomStatus(row); + if (_role == Role::Admins) { + // Perhaps we've added a new admin from search. + delegate()->peerListPrependRowFromSearchResult(row); + } + return false; + } + delegate()->peerListPrependRow(createRow(user)); + if (_role != Role::Kicked) { + setDescriptionText(QString()); + } + return true; +} + +bool ParticipantsBoxController::removeRow(not_null user) { + if (auto row = delegate()->peerListFindRow(user->id)) { + if (_role == Role::Admins) { + // Perhaps we are removing an admin from search results. + row->setCustomStatus(lang(lng_channel_admin_status_not_admin)); + delegate()->peerListConvertRowToSearchResult(row); + } else { + delegate()->peerListRemoveRow(row); + } + if (_role != Role::Kicked + && !delegate()->peerListFullRowsCount()) { + setDescriptionText(lang(lng_blocked_list_not_found)); + } + return true; + } + return false; +} + +std::unique_ptr ParticipantsBoxController::createRow( + not_null user) const { + if (_role == Role::Profile) { + return std::make_unique(user, computeType(user)); + } + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + auto row = std::make_unique(user); + refreshCustomStatus(row.get()); + if (_role == Role::Admins + && canEditAdminByRights(user) + && _additional.adminRights.find(user) + != _additional.adminRights.cend()) { + row->setActionLink(lang(lng_profile_kick)); + } else 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 ((chat + && chat->canBanMembers() + && !chat->admins.contains(user)) + || (channel + && 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); +} + +auto ParticipantsBoxController::computeType( + not_null user) const -> Type { + auto isCreator = (user == _additional.creator); + auto isAdmin = (_additional.adminRights.find(user) != _additional.adminRights.cend()); + + auto result = Type(); + result.rights = isCreator + ? Rights::Creator + : isAdmin + ? Rights::Admin + : Rights::Normal; + result.canRemove = canRestrictUser(user); + return result; +} + +void ParticipantsBoxController::recomputeTypeFor( + not_null user) { + if (_role != Role::Profile) { + return; + } + if (auto row = delegate()->peerListFindRow(user->id)) { + static_cast(row)->setType(computeType(user)); + } +} + +void ParticipantsBoxController::refreshCustomStatus(not_null row) const { + auto user = row->peer()->asUser(); + if (_role == Role::Admins) { + auto promotedBy = _additional.adminPromotedBy.find(user); + if (promotedBy == _additional.adminPromotedBy.cend()) { + if (user == _additional.creator) { + row->setCustomStatus(lang(lng_channel_admin_status_creator)); + } else { + row->setCustomStatus(lang(lng_channel_admin_status_not_admin)); + } + } else { + row->setCustomStatus(lng_channel_admin_status_promoted_by(lt_user, App::peerName(promotedBy->second))); + } + } else if (_role == Role::Kicked || _role == Role::Restricted) { + auto restrictedBy = _additional.restrictedBy.find(user); + if (restrictedBy == _additional.restrictedBy.cend()) { + row->setCustomStatus(lng_channel_banned_status_restricted_by(lt_user, "Unknown")); + } else { + row->setCustomStatus(lng_channel_banned_status_restricted_by(lt_user, App::peerName(restrictedBy->second))); + } + } +} + +ParticipantsBoxSearchController::ParticipantsBoxSearchController(not_null channel, Role role, not_null additional) +: _channel(channel) +, _role(role) +, _additional(additional) { + _timer.setCallback([this] { searchOnServer(); }); +} + +void ParticipantsBoxSearchController::searchQuery(const QString &query) { + if (_query != query) { + _query = query; + _offset = 0; + _requestId = 0; + _allLoaded = false; + if (!_query.isEmpty() && !searchInCache()) { + _timer.callOnce(AutoSearchTimeout); + } else { + _timer.cancel(); + } + } +} + +auto ParticipantsBoxSearchController::saveState() const +-> std::unique_ptr { + auto result = std::make_unique(); + result->query = _query; + result->offset = _offset; + result->allLoaded = _allLoaded; + result->wasLoading = (_requestId != 0); + return std::move(result); +} + +void ParticipantsBoxSearchController::restoreState( + std::unique_ptr state) { + if (auto my = dynamic_cast(state.get())) { + if (auto requestId = base::take(_requestId)) { + request(requestId).cancel(); + } + _cache.clear(); + _queries.clear(); + + _allLoaded = my->allLoaded; + _offset = my->offset; + _query = my->query; + if (my->wasLoading) { + searchOnServer(); + } + } +} + +void ParticipantsBoxSearchController::searchOnServer() { + Expects(!_query.isEmpty()); + loadMoreRows(); +} + +bool ParticipantsBoxSearchController::isLoading() { + return _timer.isActive() || _requestId; +} + +bool ParticipantsBoxSearchController::searchInCache() { + auto it = _cache.find(_query); + if (it != _cache.cend()) { + _requestId = 0; + searchDone(_requestId, it->second.result, it->second.requestedCount); + return true; + } + return false; +} + +bool ParticipantsBoxSearchController::loadMoreRows() { + if (_query.isEmpty()) { + return false; + } + if (!_allLoaded && !isLoading()) { + auto filter = [this] { + switch (_role) { + case Role::Admins: // Search for members, appoint as admin on found. + case Role::Profile: + 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; + auto participantsHash = 0; + + _requestId = request(MTPchannels_GetParticipants( + _channel->inputChannel, + filter, + MTP_int(_offset), + MTP_int(perPage), + MTP_int(participantsHash) + )).done([this, perPage](const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { + searchDone(requestId, result, perPage); + }).fail([this](const RPCError &error, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + _allLoaded = true; + delegate()->peerListSearchRefreshRows(); + } + }).send(); + + auto entry = Query(); + entry.text = _query; + entry.offset = _offset; + _queries.emplace(_requestId, entry); + } + return true; +} + +void ParticipantsBoxSearchController::searchDone( + mtpRequestId requestId, + const MTPchannels_ChannelParticipants &result, + int requestedCount) { + auto query = _query; + if (requestId) { + Auth().api().parseChannelParticipants(_channel, result, [&](auto&&...) { + auto it = _queries.find(requestId); + if (it != _queries.cend()) { + query = it->second.text; + if (it->second.offset == 0) { + auto &entry = _cache[query]; + entry.result = result; + entry.requestedCount = requestedCount; + } + _queries.erase(it); + } + }); + } + + if (_requestId != requestId) { + return; + } + + _requestId = 0; + TLHelp::VisitChannelParticipants(result, base::overload([&]( + const MTPDchannels_channelParticipants &data) { + auto &list = data.vparticipants.v; + if (list.size() < requestedCount) { + // We want cache to have full information about a query with small + // results count (if we don't need the second request). So we don't + // wait for an empty results list unlike the non-search peer list. + _allLoaded = true; + } + auto parseRole = (_role == Role::Admins) ? Role::Members : _role; + for_const (auto &participant, list) { + ParticipantsBoxController::HandleParticipant(participant, parseRole, _additional, [this](not_null user) { + delegate()->peerListSearchAddRow(user); + }); + } + _offset += list.size(); + }, [&](mtpTypeId type) { + _allLoaded = true; + })); + + delegate()->peerListSearchRefreshRows(); +} diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.h b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h similarity index 59% rename from Telegram/SourceFiles/profile/profile_channel_controllers.h rename to Telegram/SourceFiles/boxes/peers/edit_participants_box.h index 545eec89d..ef37b4721 100644 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h @@ -18,8 +18,6 @@ namespace Window { class Navigation; } // namespace Window -namespace Profile { - Fn SaveAdminCallback( @@ -28,6 +26,34 @@ Fn onDone, Fn onFail); +Fn SaveRestrictedCallback( + not_null channel, + not_null user, + Fn onDone, + Fn onFail); + +class ParticipantsOnlineSorter : private base::Subscriber { +public: + ParticipantsOnlineSorter( + not_null peer, + not_null delegate); + + void sort(); + rpl::producer onlineCountValue() const; + +private: + void sortDelayed(); + void refreshOnlineCount(); + + not_null _peer; + not_null _delegate; + base::Timer _sortByOnlineTimer; + rpl::variable _onlineCount = 0; + +}; + // Viewing admins, banned or restricted users list with search. class ParticipantsBoxController : public PeerListController @@ -44,10 +70,12 @@ public: }; static void Start( not_null navigation, - not_null channel, + not_null peer, Role role); struct Additional { + void fillCreator(not_null peer); + std::map, MTPChatAdminRights> adminRights; std::set> adminCanEdit; std::map, not_null> adminPromotedBy; @@ -61,11 +89,9 @@ public: ParticipantsBoxController( not_null navigation, - not_null channel, + not_null peer, Role role); - void addNewItem(); - void prepare() override; void rowClicked(not_null row) override; void rowActionClicked(not_null row) override; @@ -94,14 +120,16 @@ public: rpl::producer onlineCountValue() const override; protected: - virtual std::unique_ptr createRow(not_null user) const; + virtual std::unique_ptr createRow( + not_null user) const; private: using Row = Info::Profile::MemberListRow; using Type = Row::Type; using Rights = Row::Rights; struct SavedState : SavedStateBase { - std::unique_ptr searchState; + using SearchStateBase = PeerListSearchController::SavedStateBase; + std::unique_ptr searchState; int offset = 0; bool allLoaded = false; bool wasLoading = false; @@ -110,15 +138,19 @@ private: }; static std::unique_ptr CreateSearchController( - not_null channel, + not_null peer, Role role, not_null additional); + void prepareChatRows(not_null chat); + void rebuildChatRows(not_null chat); + void rebuildRowTypes(); + + void addNewItem(); + void addNewParticipants(); + void setNonEmptyDescription(); - void setupSortByOnline(); void setupListChangeViewers(); - void sortByOnlineDelayed(); - void sortByOnline(); void showAdmin(not_null user); void editAdminDone( not_null user, @@ -145,18 +177,16 @@ private: bool canEditAdminByRights(not_null user) const; not_null _navigation; - not_null _channel; + not_null _peer; Role _role = Role::Admins; int _offset = 0; mtpRequestId _loadRequestId = 0; bool _allLoaded = false; Additional _additional; + std::unique_ptr _onlineSorter; QPointer _editBox; QPointer _addBox; - base::Timer _sortByOnlineTimer; - rpl::variable _onlineCount = 0; - }; // Members, banned and restricted users server side search. @@ -167,7 +197,10 @@ public: using Role = ParticipantsBoxController::Role; using Additional = ParticipantsBoxController::Additional; - ParticipantsBoxSearchController(not_null channel, Role role, not_null additional); + ParticipantsBoxSearchController( + not_null channel, + Role role, + not_null additional); void searchQuery(const QString &query) override; bool isLoading() override; @@ -212,99 +245,3 @@ private: std::map _queries; }; - -// Adding an admin, banned or restricted user from channel members with search + contacts search + global search. -class AddParticipantBoxController : public PeerListController, private base::Subscriber, private MTP::Sender, public base::has_weak_ptr { -public: - using Role = ParticipantsBoxController::Role; - using Additional = ParticipantsBoxController::Additional; - - using AdminDoneCallback = Fn user, const MTPChatAdminRights &adminRights)>; - using BannedDoneCallback = Fn user, const MTPChatBannedRights &bannedRights)>; - AddParticipantBoxController(not_null channel, Role role, AdminDoneCallback adminDoneCallback, BannedDoneCallback bannedDoneCallback); - - void prepare() override; - void rowClicked(not_null row) override; - void loadMoreRows() override; - - std::unique_ptr createSearchRow(not_null peer) override; - - // Callback(not_null) - template - static void HandleParticipant(const MTPChannelParticipant &participant, not_null additional, Callback callback); - -private: - template - bool checkInfoLoaded(not_null user, Callback callback); - - void showAdmin(not_null user, bool sure = false); - void editAdminDone(not_null user, const MTPChatAdminRights &rights); - void showRestricted(not_null user, bool sure = false); - void editRestrictedDone(not_null user, const MTPChatBannedRights &rights); - void kickUser(not_null user, bool sure = false); - void restrictUserSure(not_null user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights); - bool appendRow(not_null user); - bool prependRow(not_null user); - std::unique_ptr createRow(not_null user) const; - - not_null _channel; - Role _role = Role::Admins; - int _offset = 0; - mtpRequestId _loadRequestId = 0; - bool _allLoaded = false; - Additional _additional; - QPointer _editBox; - AdminDoneCallback _adminDoneCallback; - BannedDoneCallback _bannedDoneCallback; - -}; - -// Finds channel members, then contacts, then global search results. -class AddParticipantBoxSearchController : public PeerListSearchController, private MTP::Sender { -public: - using Role = ParticipantsBoxController::Role; - using Additional = ParticipantsBoxController::Additional; - - AddParticipantBoxSearchController(not_null channel, not_null additional); - - void searchQuery(const QString &query) override; - bool isLoading() override; - bool loadMoreRows() override; - -private: - struct CacheEntry { - MTPchannels_ChannelParticipants result; - int requestedCount = 0; - }; - struct Query { - QString text; - int offset = 0; - }; - - void searchOnServer(); - bool searchParticipantsInCache(); - void searchParticipantsDone(mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount); - bool searchGlobalInCache(); - void searchGlobalDone(mtpRequestId requestId, const MTPcontacts_Found &result); - void requestParticipants(); - void addChatsContacts(); - void requestGlobal(); - - not_null _channel; - not_null _additional; - - base::Timer _timer; - QString _query; - mtpRequestId _requestId = 0; - int _offset = 0; - bool _participantsLoaded = false; - bool _chatsContactsAdded = false; - bool _globalLoaded = false; - std::map _participantsCache; - std::map _participantsQueries; - std::map _globalCache; - std::map _globalQueries; - -}; - -} // namespace Profile diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 7618a4620..6724ed707 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_values.h" -#include "profile/profile_channel_controllers.h" +#include "boxes/peers/edit_participants_box.h" #include "boxes/peers/manage_peer_box.h" #include "window/window_controller.h" #include "mainwindow.h" @@ -271,9 +271,7 @@ void EditPeerPermissionsBox::prepare() { inner->add(std::move(checkboxes)); - if (const auto channel = _peer->asChannel()) { - addBannedButtons(inner, channel); - } + addBannedButtons(inner); _value = getRestrictions; _save = addButton(langFactory(lng_settings_save)); @@ -283,41 +281,40 @@ void EditPeerPermissionsBox::prepare() { } void EditPeerPermissionsBox::addBannedButtons( - not_null container, - not_null channel) { - using Profile::ParticipantsBoxController; - + not_null container) { container->add( object_ptr(container), { 0, st::infoProfileSkip, 0, st::infoProfileSkip }); const auto navigation = App::wnd()->controller(); + const auto channel = _peer->asChannel(); ManagePeerBox::CreateButton( container, Lang::Viewer(lng_manage_peer_exceptions), - Info::Profile::RestrictedCountValue(channel) - | ToPositiveNumberString(), + (channel + ? Info::Profile::RestrictedCountValue(channel) + : rpl::single(0)) | ToPositiveNumberString(), [=] { ParticipantsBoxController::Start( navigation, - channel, + _peer, ParticipantsBoxController::Role::Restricted); }, - st::peerPermissionsButton, - st::infoIconRestrictedUsers); - ManagePeerBox::CreateButton( - container, - Lang::Viewer(lng_manage_peer_removed_users), - Info::Profile::KickedCountValue(channel) - | ToPositiveNumberString(), - [=] { - ParticipantsBoxController::Start( - navigation, - channel, - ParticipantsBoxController::Role::Kicked); - }, - st::peerPermissionsButton, - st::infoIconBlacklist); + st::peerPermissionsButton); + if (channel) { + ManagePeerBox::CreateButton( + container, + Lang::Viewer(lng_manage_peer_removed_users), + Info::Profile::KickedCountValue(channel) + | ToPositiveNumberString(), + [=] { + ParticipantsBoxController::Start( + navigation, + _peer, + ParticipantsBoxController::Role::Kicked); + }, + st::peerPermissionsButton); + } } template diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h index 488c100a4..555991ced 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h @@ -27,9 +27,7 @@ protected: void prepare() override; private: - void addBannedButtons( - not_null container, - not_null channel); + void addBannedButtons(not_null container); not_null _peer; Ui::RoundButton *_save = nullptr; diff --git a/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp index fd587a1c7..38134cfe4 100644 --- a/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp @@ -11,11 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "boxes/peers/edit_peer_info_box.h" #include "boxes/peers/edit_peer_permissions_box.h" +#include "boxes/peers/edit_participants_box.h" #include "ui/wrap/vertical_layout.h" #include "ui/widgets/labels.h" #include "history/admin_log/history_admin_log_section.h" #include "window/window_controller.h" -#include "profile/profile_channel_controllers.h" #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_values.h" @@ -52,7 +52,7 @@ Info::Profile::Button *AddButton( rpl::single(QString()), std::move(callback), st::managePeerButton, - icon); + &icon); } void AddButtonWithCount( @@ -67,7 +67,7 @@ void AddButtonWithCount( std::move(count), std::move(callback), st::managePeerButton, - icon); + &icon); } bool HasRecentActions(not_null channel) { @@ -139,10 +139,10 @@ void FillManageChatBox( Info::Profile::AdminsCountValue(chat) | ToPositiveNumberString(), [=] { - //ParticipantsBoxController::Start( - // navigation, - // channel, - // ParticipantsBoxController::Role::Admins); + ParticipantsBoxController::Start( + navigation, + chat, + ParticipantsBoxController::Role::Admins); }, st::infoIconAdministrators); AddButtonWithCount( @@ -151,10 +151,10 @@ void FillManageChatBox( Info::Profile::MembersCountValue(chat) | ToPositiveNumberString(), [=] { - //ParticipantsBoxController::Start( - // navigation, - // channel, - // ParticipantsBoxController::Role::Members); + ParticipantsBoxController::Start( + navigation, + chat, + ParticipantsBoxController::Role::Members); }, st::infoIconMembers); } @@ -164,8 +164,6 @@ void FillManageChannelBox( not_null navigation, not_null channel, not_null content) { - using Profile::ParticipantsBoxController; - auto isGroup = channel->isMegagroup(); if (HasEditInfoBox(channel)) { AddButton( @@ -272,17 +270,19 @@ Info::Profile::Button *ManagePeerBox::CreateButton( rpl::producer &&count, Fn callback, const style::InfoProfileCountButton &st, - const style::icon &icon) { + const style::icon *icon) { const auto button = parent->add( object_ptr( parent, std::move(text), st.button)); button->addClickHandler(callback); - Ui::CreateChild( - button, - icon, - st.iconPosition); + if (icon) { + Ui::CreateChild( + button, + *icon, + st.iconPosition); + } const auto label = Ui::CreateChild( button, std::move(count), diff --git a/Telegram/SourceFiles/boxes/peers/manage_peer_box.h b/Telegram/SourceFiles/boxes/peers/manage_peer_box.h index 93cecc535..631ecbdea 100644 --- a/Telegram/SourceFiles/boxes/peers/manage_peer_box.h +++ b/Telegram/SourceFiles/boxes/peers/manage_peer_box.h @@ -35,7 +35,7 @@ public: rpl::producer &&count, Fn callback, const style::InfoProfileCountButton &st, - const style::icon &icon); + const style::icon *icon = nullptr); protected: void prepare() override; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index a85416489..d840a2bdf 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -105,12 +105,23 @@ void ChannelData::setKickedCount(int newKickedCount) { MTPChatBannedRights ChannelData::KickedRestrictedRights() { using Flag = MTPDchatBannedRights::Flag; - auto flags = Flag::f_view_messages | Flag::f_send_messages | Flag::f_send_media | Flag::f_embed_links | Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_games | Flag::f_send_inline; - return MTP_chatBannedRights(MTP_flags(flags), MTP_int(std::numeric_limits::max())); + const auto flags = Flag::f_view_messages + | Flag::f_send_messages + | Flag::f_send_media + | Flag::f_embed_links + | Flag::f_send_stickers + | Flag::f_send_gifs + | Flag::f_send_games + | Flag::f_send_inline; + return MTP_chatBannedRights( + MTP_flags(flags), + MTP_int(std::numeric_limits::max())); } -void ChannelData::applyEditAdmin(not_null user, const MTPChatAdminRights &oldRights, const MTPChatAdminRights &newRights) { - auto flags = Notify::PeerUpdate::Flag::AdminsChanged | Notify::PeerUpdate::Flag::None; +void ChannelData::applyEditAdmin( + not_null user, + const MTPChatAdminRights &oldRights, + const MTPChatAdminRights &newRights) { if (mgInfo) { // If rights are empty - still add participant? TODO check if (!base::contains(mgInfo->lastParticipants, user)) { @@ -167,7 +178,9 @@ void ChannelData::applyEditAdmin(not_null user, const MTPChatAdminRig setAdminsCount(adminsCount() + 1); updateFullForced(); } - Notify::peerUpdatedDelayed(this, flags); + Notify::peerUpdatedDelayed( + this, + Notify::PeerUpdate::Flag::AdminsChanged); } void ChannelData::applyEditBanned(not_null user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights) { @@ -285,23 +298,22 @@ void ChannelData::setFeedPointer(Data::Feed *feed) { } bool ChannelData::canBanMembers() const { - return (adminRights() & AdminRight::f_ban_users) - || amCreator(); + return amCreator() + || (adminRights() & AdminRight::f_ban_users); } bool ChannelData::canEditMessages() const { - return (adminRights() & AdminRight::f_edit_messages) - || amCreator(); + return amCreator() + || (adminRights() & AdminRight::f_edit_messages); } bool ChannelData::canDeleteMessages() const { - return (adminRights() & AdminRight::f_delete_messages) - || amCreator(); + return amCreator() + || (adminRights() & AdminRight::f_delete_messages); } bool ChannelData::anyoneCanAddMembers() const { - // #TODO groups - return false;// (flags() & MTPDchannel::Flag::f_democracy); + return !(defaultRestrictions() & Restriction::f_invite_users); } bool ChannelData::hiddenPreHistory() const { @@ -312,6 +324,10 @@ bool ChannelData::canAddMembers() const { return !amRestricted(ChatRestriction::f_invite_users); } +bool ChannelData::canSendPolls() const { + return canWrite() && !amRestricted(ChatRestriction::f_send_polls); +} + bool ChannelData::canAddAdmins() const { return amCreator() || (adminRights() & AdminRight::f_add_admins); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index e14a7b60f..637e2d2a4 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -246,13 +246,14 @@ public: bool canEditUsername() const; bool canEditPreHistoryHidden() const; bool canAddMembers() const; - + bool canAddAdmins() const; bool canBanMembers() const; + bool canSendPolls() const; + bool anyoneCanAddMembers() const; + bool canEditMessages() const; bool canDeleteMessages() const; - bool anyoneCanAddMembers() const; bool hiddenPreHistory() const; - bool canAddAdmins() const; bool canPublish() const; bool canViewMembers() const; bool canViewAdmins() const; diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index bc7d56247..52649bd70 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -37,15 +37,24 @@ bool ChatData::actionsUnavailable() const { return isDeactivated() || !amIn(); } +auto ChatData::DefaultAdminRights() -> AdminRights { + using Flag = AdminRight; + return Flag::f_change_info + | Flag::f_delete_messages + | Flag::f_ban_users + | Flag::f_invite_users + | Flag::f_pin_messages; +} + bool ChatData::canWrite() const { // Duplicated in Data::CanWriteValue(). return !actionsUnavailable() - && !amRestricted(ChatRestriction::f_send_messages); + && !amRestricted(Restriction::f_send_messages); } bool ChatData::canEditInformation() const { return !actionsUnavailable() - && !amRestricted(ChatRestriction::f_change_info); + && !amRestricted(Restriction::f_change_info); } bool ChatData::canEditPermissions() const { @@ -64,13 +73,45 @@ bool ChatData::canEditPreHistoryHidden() const { bool ChatData::canAddMembers() const { return !actionsUnavailable() - && !amRestricted(ChatRestriction::f_invite_users); + && !amRestricted(Restriction::f_invite_users); +} + +bool ChatData::canSendPolls() const { + return !actionsUnavailable() + && !amRestricted(Restriction::f_send_polls); +} + +bool ChatData::canAddAdmins() const { + return !actionsUnavailable() + && amCreator(); +} + +bool ChatData::canBanMembers() const { + return amCreator() + || (adminRights() & AdminRight::f_ban_users); +} + +bool ChatData::anyoneCanAddMembers() const { + return !(defaultRestrictions() & Restriction::f_invite_users); } void ChatData::setName(const QString &newName) { updateNameDelayed(newName.isEmpty() ? name : newName, QString(), QString()); } +void ChatData::applyEditAdmin(not_null user, bool isAdmin) { + auto flags = Notify::PeerUpdate::Flag::AdminsChanged + | Notify::PeerUpdate::Flag::None; + if (isAdmin) { + admins.emplace(user); + } else { + admins.remove(user); + } + Notify::peerUpdatedDelayed( + this, + Notify::PeerUpdate::Flag::AdminsChanged); +} + void ChatData::invalidateParticipants() { // #TODO groups participants.clear(); diff --git a/Telegram/SourceFiles/data/data_chat.h b/Telegram/SourceFiles/data/data_chat.h index fb7d3cfa3..d6f248b3d 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -121,13 +121,21 @@ public: return flags() & MTPDchat::Flag::f_migrated_to; } - // Like in ChatData. + static AdminRights DefaultAdminRights(); + + // Like in ChannelData. bool canWrite() const; bool canEditInformation() const; bool canEditPermissions() const; bool canEditUsername() const; bool canEditPreHistoryHidden() const; bool canAddMembers() const; + bool canAddAdmins() const; + bool canBanMembers() const; + bool canSendPolls() const; + bool anyoneCanAddMembers() const; + + void applyEditAdmin(not_null user, bool isAdmin); void setInviteLink(const QString &newInviteLink); QString inviteLink() const { diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index d42a70d13..6ca5d33de 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -1253,6 +1253,13 @@ TextWithEntities MediaPoll::clipboardText() const { return { text, EntitiesInText() }; } +QString MediaPoll::errorTextForForward(not_null peer) const { + if (peer->amRestricted(ChatRestriction::f_send_polls)) { + return lang(lng_restricted_send_polls); + } + return QString(); +} + bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index fac409708..806b8ddd1 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -389,6 +389,7 @@ public: QString notificationText() const override; QString pinnedTextSubstring() const override; TextWithEntities clipboardText() const override; + QString errorTextForForward(not_null peer) const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index b159108ad..c53567464 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -435,14 +435,15 @@ void PeerData::fillNames() { PeerData::~PeerData() = default; void PeerData::updateFull() { - if (!_lastFullUpdate || getms(true) > _lastFullUpdate + kUpdateFullPeerTimeout) { + if (!_lastFullUpdate + || getms(true) > _lastFullUpdate + kUpdateFullPeerTimeout) { updateFullForced(); } } void PeerData::updateFullForced() { session().api().requestFullPeer(this); - if (auto channel = asChannel()) { + if (const auto channel = asChannel()) { if (!channel->amCreator() && !channel->inviter) { session().api().requestSelfParticipant(channel); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp index 48a84478d..78ae32bc3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "data/data_peer_values.h" +#include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" #include "observer_peer.h" diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h index 2198375f2..9a7a12c9d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h +++ b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "boxes/peer_list_box.h" -#include "profile/profile_channel_controllers.h" +#include "boxes/peers/edit_participants_box.h" namespace Dialogs { @@ -38,7 +38,7 @@ private: }; -class ChannelSearchFromController : public Profile::ParticipantsBoxController { +class ChannelSearchFromController : public ParticipantsBoxController { public: ChannelSearchFromController( not_null navigation, diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index be044276b..c4d990085 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -26,10 +26,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "messenger.h" #include "boxes/peer_list_box.h" +#include "boxes/peers/edit_participants_box.h" #include "window/window_controller.h" #include "window/window_slide_animation.h" #include "window/window_connecting_widget.h" -#include "profile/profile_channel_controllers.h" #include "storage/storage_media_prepare.h" #include "storage/localstorage.h" #include "data/data_session.h" diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index efd856a58..9f1b9f102 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -213,7 +213,7 @@ QString GenerateBannedChangeText( { Flag::f_send_media, lng_admin_log_banned_send_media }, { Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_inline | Flag::f_send_games, lng_admin_log_banned_send_stickers }, { Flag::f_embed_links, lng_admin_log_banned_embed_links }, - { Flag::f_send_media, lng_admin_log_banned_send_polls }, + { Flag::f_send_polls, lng_admin_log_banned_send_polls }, { Flag::f_change_info, lng_admin_log_admin_change_info }, { Flag::f_invite_users, lng_admin_log_admin_invite_users }, { Flag::f_pin_messages, lng_admin_log_admin_pin_messages }, diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index fd88ed4fa..b89bb2f6d 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -361,7 +361,6 @@ infoIconMediaRound: icon {{ "info_media_round", infoIconFg }}; infoIconRecentActions: icon {{ "info_recent_actions", infoIconFg }}; infoIconAdministrators: icon {{ "info_administrators", infoIconFg }}; infoIconBlacklist: icon {{ "info_blacklist", infoIconFg }}; -infoIconRestrictedUsers: icon {{ "info_restricted_users", infoIconFg }}; infoIconPermissions: icon {{ "info_permissions", infoIconFg }}; infoInformationIconPosition: point(25px, 12px); infoNotificationsIconPosition: point(20px, 5px); @@ -592,7 +591,7 @@ managePeerButton: InfoProfileCountButton { } peerPermissionsButton: InfoProfileCountButton(managePeerButton) { button: InfoProfileButton(infoProfileButton) { - padding: margins(67px, 12px, 67px, 10px); + padding: margins(24px, 12px, 24px, 10px); } iconPosition: point(24px, 5px); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.cpp b/Telegram/SourceFiles/info/profile/info_profile_members.cpp index fd42f8237..731cd0d5b 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_members.cpp @@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/search_field_controller.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" -#include "boxes/peer_list_controllers.h" +#include "boxes/peers/add_participants_box.h" #include "window/window_controller.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -323,11 +323,7 @@ void Members::updateHeaderControlsGeometry(int newWidth) { void Members::addMember() { if (const auto chat = _peer->asChat()) { - if (chat->count >= Global::ChatSizeMax() && chat->amCreator()) { - // #TODO convert and add inside AddParticipantsBoxController? - } else { - AddParticipantsBoxController::Start(chat); - } + AddParticipantsBoxController::Start(chat); } else if (const auto channel = _peer->asChannel()) { const auto state = _listController->saveState(); const auto users = ranges::view::all( diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.h b/Telegram/SourceFiles/info/profile/info_profile_members.h index 2be980c70..12e618841 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members.h +++ b/Telegram/SourceFiles/info/profile/info_profile_members.h @@ -19,10 +19,6 @@ struct ScrollToRequest; class AbstractButton; } // namespace Ui -namespace Profile { -class ParticipantsBoxController; -} // namespace Profile - namespace Info { class Controller; diff --git a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp index fe8395fe5..6c7a85160 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp @@ -9,13 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "base/weak_ptr.h" -#include "profile/profile_channel_controllers.h" +#include "boxes/peers/edit_participants_box.h" #include "ui/widgets/popup_menu.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "auth_session.h" #include "mainwidget.h" #include "observer_peer.h" +#include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" #include "boxes/confirm_box.h" @@ -378,17 +379,15 @@ void MemberListRow::paintNameIcon( std::unique_ptr CreateMembersController( not_null navigation, not_null peer) { - if (auto chat = peer->asChat()) { + if (const auto chat = peer->asChat()) { return std::make_unique( navigation, chat); - } else if (auto channel = peer->asChannel()) { - using ChannelMembersController - = ::Profile::ParticipantsBoxController; - return std::make_unique( + } else if (const auto channel = peer->asChannel()) { + return std::make_unique( navigation, channel, - ChannelMembersController::Role::Profile); + ParticipantsBoxController::Role::Profile); } Unexpected("Peer type in CreateMembersController()"); } diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index c0ce58bb9..d393af269 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "boxes/confirm_box.h" #include "boxes/peers/edit_participant_box.h" -#include "profile/profile_channel_controllers.h" +#include "boxes/peers/edit_participants_box.h" #include "ui/widgets/popup_menu.h" #include "data/data_peer_values.h" #include "data/data_channel.h" @@ -68,51 +68,6 @@ GroupMembersWidget::GroupMembersWidget( refreshMembers(); } -void GroupMembersWidget::editAdmin(not_null user) { - auto megagroup = peer()->asMegagroup(); - if (!megagroup) { - return; // not supported - } - auto currentRightsIt = megagroup->mgInfo->lastAdmins.find(user); - auto hasAdminRights = (currentRightsIt != megagroup->mgInfo->lastAdmins.cend()); - auto currentRights = hasAdminRights ? currentRightsIt->second.rights : MTP_chatAdminRights(MTP_flags(0)); - auto weak = std::make_shared>(nullptr); - auto box = Box(megagroup, user, currentRights); - box->setSaveCallback(SaveAdminCallback(megagroup, user, [=]( - const MTPChatAdminRights &newRights) { - if (*weak) { - (*weak)->closeBox(); - } - }, [=] { - if (*weak) { - (*weak)->closeBox(); - } - })); - *weak = Ui::show(std::move(box)); -} - -void GroupMembersWidget::restrictUser(not_null user) { - auto megagroup = peer()->asMegagroup(); - if (!megagroup) { - return; // not supported - } - auto currentRightsIt = megagroup->mgInfo->lastRestricted.find(user); - auto currentRights = (currentRightsIt != megagroup->mgInfo->lastRestricted.end()) - ? currentRightsIt->second.rights - : MTP_chatBannedRights(MTP_flags(0), MTP_int(0)); - auto hasAdminRights = megagroup->mgInfo->lastAdmins.find(user) != megagroup->mgInfo->lastAdmins.cend(); - auto box = Box(megagroup, user, hasAdminRights, currentRights); - box->setSaveCallback([megagroup, user](const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights) { - Ui::hideLayer(); - // #TODO use Auth().api(). - MTP::send(MTPchannels_EditBanned(megagroup->inputChannel, user->inputUser, newRights), rpcDone([megagroup, user, oldRights, newRights](const MTPUpdates &result) { - Auth().api().applyUpdates(result); - megagroup->applyEditBanned(user, oldRights, newRights); - })); - }); - Ui::show(std::move(box)); -} - void GroupMembersWidget::removePeer(PeerData *selectedPeer) { auto user = selectedPeer->asUser(); Assert(user != nullptr); diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.h b/Telegram/SourceFiles/profile/profile_block_group_members.h index efe2e701e..297f15e54 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.h +++ b/Telegram/SourceFiles/profile/profile_block_group_members.h @@ -42,8 +42,6 @@ private: // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); - void editAdmin(not_null user); - void restrictUser(not_null user); void removePeer(PeerData *selectedPeer); void refreshMembers(); void fillChatMembers(ChatData *chat); diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp deleted file mode 100644 index 8c3bc55cf..000000000 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp +++ /dev/null @@ -1,2000 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include "profile/profile_channel_controllers.h" - -#include "boxes/peer_list_controllers.h" -#include "boxes/peers/edit_participant_box.h" -#include "boxes/confirm_box.h" -#include "boxes/add_contact_box.h" -#include "core/tl_help.h" -#include "base/overload.h" -#include "auth_session.h" -#include "apiwrap.h" -#include "lang/lang_keys.h" -#include "mainwidget.h" -#include "observer_peer.h" -#include "dialogs/dialogs_indexed_list.h" -#include "data/data_peer_values.h" -#include "data/data_session.h" -#include "data/data_channel.h" -#include "data/data_user.h" -#include "ui/widgets/popup_menu.h" -#include "window/window_controller.h" -#include "history/history.h" - -namespace Profile { -namespace { - -constexpr auto kParticipantsFirstPageCount = 16; -constexpr auto kParticipantsPerPage = 200; -constexpr auto kSortByOnlineDelay = TimeMs(1000); - -void RemoveAdmin( - not_null channel, - not_null user, - const MTPChatAdminRights &oldRights, - Fn onDone) { - const auto newRights = MTP_chatAdminRights(MTP_flags(0)); - auto done = [=](const MTPUpdates &result) { - Auth().api().applyUpdates(result); - channel->applyEditAdmin(user, oldRights, newRights); - onDone(); - }; - MTP::send( - MTPchannels_EditAdmin( - channel->inputChannel, - user->inputUser, - newRights), - rpcDone(std::move(done))); -} - -} // namespace - -Fn SaveAdminCallback( - not_null channel, - not_null user, - Fn onDone, - Fn onFail) { - return [=]( - const MTPChatAdminRights &oldRights, - const MTPChatAdminRights &newRights) { - auto done = [=](const MTPUpdates &result) { - Auth().api().applyUpdates(result); - channel->applyEditAdmin(user, oldRights, newRights); - onDone(newRights); - }; - auto fail = [=](const RPCError &error) { - if (MTP::isDefaultHandledError(error)) { - return false; - } - if (error.type() == qstr("USER_NOT_MUTUAL_CONTACT")) { - Ui::show( - Box(PeerFloodErrorText( - channel->isMegagroup() - ? PeerFloodType::InviteGroup - : PeerFloodType::InviteChannel)), - LayerOption::KeepOther); - } else if (error.type() == qstr("BOT_GROUPS_BLOCKED")) { - Ui::show( - Box(lang(lng_error_cant_add_bot)), - LayerOption::KeepOther); - } else if (error.type() == qstr("ADMINS_TOO_MUCH")) { - Ui::show( - Box(lang(channel->isMegagroup() - ? lng_error_admin_limit - : lng_error_admin_limit_channel)), - LayerOption::KeepOther); - } - onFail(); - return true; - }; - MTP::send( - MTPchannels_EditAdmin( - channel->inputChannel, - user->inputUser, - newRights), - rpcDone(std::move(done)), - rpcFail(std::move(fail))); - }; -} - -ParticipantsBoxController::ParticipantsBoxController( - not_null navigation, - not_null channel, - Role role) -: PeerListController(CreateSearchController(channel, role, &_additional)) -, _navigation(navigation) -, _channel(channel) -, _role(role) { - if (_channel->mgInfo) { - _additional.creator = _channel->mgInfo->creator; - } - if (_role == Role::Profile) { - setupSortByOnline(); - setupListChangeViewers(); - } -} - -void ParticipantsBoxController::setupSortByOnline() { - _sortByOnlineTimer.setCallback([this] { sortByOnline(); }); - using UpdateFlag = Notify::PeerUpdate::Flag; - subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler( - UpdateFlag::UserOnlineChanged, - [this](const Notify::PeerUpdate &update) { - if (auto row = delegate()->peerListFindRow( - update.peer->id)) { - row->refreshStatus(); - sortByOnlineDelayed(); - } - })); -} - -void ParticipantsBoxController::setupListChangeViewers() { - if (!_channel->isMegagroup()) { - return; - } - Auth().data().megagroupParticipantAdded( - _channel - ) | rpl::start_with_next([this](not_null user) { - if (delegate()->peerListFullRowsCount() > 0) { - if (delegate()->peerListRowAt(0)->peer() == user) { - return; - } - } - if (auto row = delegate()->peerListFindRow(user->id)) { - delegate()->peerListPartitionRows([user](const PeerListRow &row) { - return (row.peer() == user); - }); - } else { - delegate()->peerListPrependRow(createRow(user)); - delegate()->peerListRefreshRows(); - sortByOnline(); - } - }, lifetime()); - Auth().data().megagroupParticipantRemoved( - _channel - ) | rpl::start_with_next([this](not_null user) { - if (auto row = delegate()->peerListFindRow(user->id)) { - delegate()->peerListRemoveRow(row); - } - delegate()->peerListRefreshRows(); - }, lifetime()); -} - -void ParticipantsBoxController::sortByOnlineDelayed() { - if (!_sortByOnlineTimer.isActive()) { - _sortByOnlineTimer.callOnce(kSortByOnlineDelay); - } -} - -void ParticipantsBoxController::sortByOnline() { - if (_role != Role::Profile - || !_channel->isMegagroup() - || _channel->membersCount() > Global::ChatSizeMax()) { - _onlineCount = 0; - return; - } - auto now = unixtime(); - delegate()->peerListSortRows([now]( - const PeerListRow &a, - const PeerListRow &b) { - return Data::SortByOnlineValue(a.peer()->asUser(), now) > - Data::SortByOnlineValue(b.peer()->asUser(), now); - }); - refreshOnlineCount(); -} - -void ParticipantsBoxController::refreshOnlineCount() { - Expects(_role == Role::Profile); - Expects(_channel->membersCount() <= Global::ChatSizeMax()); - - auto now = unixtime(); - auto left = 0, right = delegate()->peerListFullRowsCount(); - while (right > left) { - auto middle = (left + right) / 2; - auto row = delegate()->peerListRowAt(middle); - if (Data::OnlineTextActive(row->peer()->asUser(), now)) { - left = middle + 1; - } else { - right = middle; - } - } - _onlineCount = left; -} - -std::unique_ptr -ParticipantsBoxController::CreateSearchController( - not_null channel, - Role role, - not_null additional) { - // In admins box complex search is used for adding new admins. - if (role != Role::Admins || channel->canAddAdmins()) { - return std::make_unique( - channel, - role, - additional); - } - return nullptr; -} - -void ParticipantsBoxController::Start( - not_null navigation, - not_null channel, - Role role) { - auto controller = std::make_unique( - navigation, - channel, - role); - auto initBox = [role, channel, controller = controller.get()](not_null box) { - box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); - auto canAddNewItem = [role, channel] { - switch (role) { - case Role::Members: return !channel->isMegagroup() && channel->canAddMembers() && (channel->membersCount() < Global::ChatSizeMax()); - case Role::Admins: return channel->canAddAdmins(); - case Role::Restricted: - case Role::Kicked: return channel->canBanMembers(); - } - Unexpected("Role value in ParticipantsBoxController::Start()"); - }; - auto addNewItemText = [role] { - switch (role) { - case Role::Members: return langFactory(lng_channel_add_members); - case Role::Admins: return langFactory(lng_channel_add_admin); - case Role::Restricted: return langFactory(lng_channel_add_restricted); - case Role::Kicked: return langFactory(lng_channel_add_banned); - } - Unexpected("Role value in ParticipantsBoxController::Start()"); - }; - if (canAddNewItem()) { - box->addLeftButton(addNewItemText(), [controller] { controller->addNewItem(); }); - } - }; - Ui::show( - Box(std::move(controller), std::move(initBox)), - LayerOption::KeepOther); -} - -void ParticipantsBoxController::addNewItem() { - Expects(_role != Role::Profile); - - if (_role == Role::Members) { - if (_channel->membersCount() >= Global::ChatSizeMax()) { - Ui::show( - Box(_channel), - LayerOption::KeepOther); - } else { - auto already = std::vector>(); - already.reserve(delegate()->peerListFullRowsCount()); - for (auto i = 0, count = delegate()->peerListFullRowsCount(); i != count; ++i) { - already.push_back(delegate()->peerListRowAt(i)->peer()->asUser()); - } - AddParticipantsBoxController::Start(_channel, { already.begin(), already.end() }); - } - return; - } - auto weak = base::make_weak(this); - _addBox = Ui::show(Box(std::make_unique(_channel, _role, [weak](not_null user, const MTPChatAdminRights &rights) { - if (const auto strong = weak.get()) { - strong->editAdminDone(user, rights); - } - }, [weak](not_null user, const MTPChatBannedRights &rights) { - if (const auto strong = weak.get()) { - strong->editRestrictedDone(user, rights); - } - }), [](not_null box) { - box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); - }), LayerOption::KeepOther); -} - -void ParticipantsBoxController::peerListSearchAddRow(not_null peer) { - PeerListController::peerListSearchAddRow(peer); - if (_role == Role::Restricted && delegate()->peerListFullRowsCount() > 0) { - setDescriptionText(QString()); - } -} - -std::unique_ptr ParticipantsBoxController::createSearchRow( - not_null peer) { - if (auto user = peer->asUser()) { - return createRow(user); - } - return nullptr; -} - -std::unique_ptr ParticipantsBoxController::createRestoredRow( - not_null peer) { - if (auto user = peer->asUser()) { - return createRow(user); - } - return nullptr; -} - -std::unique_ptr ParticipantsBoxController::saveState() const { - Expects(_role == Role::Profile); - - auto result = PeerListController::saveState(); - - auto my = std::make_unique(); - my->additional = _additional; - my->offset = _offset; - my->allLoaded = _allLoaded; - my->wasLoading = (_loadRequestId != 0); - if (auto search = searchController()) { - my->searchState = search->saveState(); - } - - if (_channel->isMegagroup()) { - auto weak = result.get(); - Auth().data().megagroupParticipantAdded( - _channel - ) | rpl::start_with_next([weak](not_null user) { - if (!weak->list.empty()) { - if (weak->list[0] == user) { - return; - } - } - auto pos = ranges::find(weak->list, user); - if (pos == weak->list.cend()) { - weak->list.push_back(user); - } - ranges::stable_partition( - weak->list, - [user](auto peer) { return (peer == user); }); - }, my->lifetime); - Auth().data().megagroupParticipantRemoved( - _channel - ) | rpl::start_with_next([weak](not_null user) { - weak->list.erase(std::remove( - weak->list.begin(), - weak->list.end(), - user), weak->list.end()); - weak->filterResults.erase(std::remove( - weak->filterResults.begin(), - weak->filterResults.end(), - user), weak->filterResults.end()); - }, my->lifetime); - } - result->controllerState = std::move(my); - return result; -} - -void ParticipantsBoxController::restoreState( - std::unique_ptr state) { - auto typeErasedState = state - ? state->controllerState.get() - : nullptr; - if (auto my = dynamic_cast(typeErasedState)) { - if (auto requestId = base::take(_loadRequestId)) { - request(requestId).cancel(); - } - - _additional = std::move(my->additional); - _offset = my->offset; - _allLoaded = my->allLoaded; - if (auto search = searchController()) { - search->restoreState(std::move(my->searchState)); - } - if (my->wasLoading) { - loadMoreRows(); - } - PeerListController::restoreState(std::move(state)); - if (delegate()->peerListFullRowsCount() > 0) { - setNonEmptyDescription(); - } else if (_allLoaded) { - setDescriptionText(lang(lng_blocked_list_not_found)); - } - sortByOnline(); - } -} - -rpl::producer ParticipantsBoxController::onlineCountValue() const { - return _onlineCount.value(); -} - -template -void ParticipantsBoxController::HandleParticipant(const MTPChannelParticipant &participant, Role role, not_null additional, Callback callback) { - if ((role == Role::Profile - || 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; - if (admin.is_can_edit()) { - additional->adminCanEdit.emplace(user); - } else { - additional->adminCanEdit.erase(user); - } - if (auto promoted = App::userLoaded(admin.vpromoted_by.v)) { - auto it = additional->adminPromotedBy.find(user); - if (it == additional->adminPromotedBy.end()) { - additional->adminPromotedBy.emplace(user, promoted); - } else { - it->second = promoted; - } - } else { - LOG(("API Error: No user %1 for admin promoted by.").arg(admin.vpromoted_by.v)); - } - callback(user); - } - } else if ((role == Role::Profile - || 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::Profile - || 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; - if (auto kickedby = App::userLoaded(banned.vkicked_by.v)) { - auto it = additional->restrictedBy.find(user); - if (it == additional->restrictedBy.end()) { - additional->restrictedBy.emplace(user, kickedby); - } else { - it->second = kickedby; - } - } - callback(user); - } - } else if ((role == Role::Profile - || 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::Profile - || 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())); - } -} - -void ParticipantsBoxController::prepare() { - auto titleKey = [this] { - switch (_role) { - case Role::Admins: return lng_channel_admins; - case Role::Profile: - case Role::Members: return lng_profile_participants_section; - case Role::Restricted: return lng_restricted_list_title; - case Role::Kicked: return lng_banned_list_title; - } - Unexpected("Role in ParticipantsBoxController::prepare()"); - }; - delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); - delegate()->peerListSetTitle(langFactory(titleKey())); - setDescriptionText(lang(lng_contacts_loading)); - setSearchNoResultsText(lang(lng_blocked_list_not_found)); - - loadMoreRows(); - delegate()->peerListRefreshRows(); -} - -void ParticipantsBoxController::loadMoreRows() { - if (searchController() && searchController()->loadMoreRows()) { - return; - } - if (_loadRequestId || _allLoaded) { - return; - } - - if (feedMegagroupLastParticipants()) { - return; - } - - auto filter = [this] { - if (_role == Role::Members || _role == Role::Profile) { - return MTP_channelParticipantsRecent(); - } else if (_role == Role::Admins) { - return MTP_channelParticipantsAdmins(); - } else if (_role == Role::Restricted) { - return MTP_channelParticipantsBanned(MTP_string(QString())); - } - return MTP_channelParticipantsKicked(MTP_string(QString())); - }(); - - // First query is small and fast, next loads a lot of rows. - auto perPage = (_offset > 0) ? kParticipantsPerPage : kParticipantsFirstPageCount; - auto participantsHash = 0; - - _loadRequestId = request(MTPchannels_GetParticipants( - _channel->inputChannel, - filter, - MTP_int(_offset), - MTP_int(perPage), - MTP_int(participantsHash) - )).done([this](const MTPchannels_ChannelParticipants &result) { - auto firstLoad = !_offset; - _loadRequestId = 0; - - auto wasRecentRequest = firstLoad - && (_role == Role::Members || _role == Role::Profile); - auto parseParticipants = [&](auto &&result, auto &&callback) { - if (wasRecentRequest) { - Auth().api().parseRecentChannelParticipants( - _channel, - result, - callback); - } else { - Auth().api().parseChannelParticipants( - _channel, - result, - callback); - } - }; - parseParticipants(result, [&]( - int availableCount, - const QVector &list) { - for (auto &participant : list) { - HandleParticipant( - participant, - _role, - &_additional, - [&](auto user) { appendRow(user); }); - } - if (auto size = list.size()) { - _offset += size; - } else { - // To be sure - wait for a whole empty result list. - _allLoaded = true; - } - }); - - if (delegate()->peerListFullRowsCount() > 0) { - sortByOnline(); - if (firstLoad) { - setNonEmptyDescription(); - } - } else if (_allLoaded) { - setDescriptionText(lang(lng_blocked_list_not_found)); - } - delegate()->peerListRefreshRows(); - }).fail([this](const RPCError &error) { - _loadRequestId = 0; - }).send(); -} - -void ParticipantsBoxController::setNonEmptyDescription() { - setDescriptionText((_role == Role::Kicked) - ? lang(_channel->isMegagroup() - ? lng_group_blocked_list_about - : lng_channel_blocked_list_about) - : QString()); -} - -bool ParticipantsBoxController::feedMegagroupLastParticipants() { - if ((_role != Role::Members && _role != Role::Profile) - || delegate()->peerListFullRowsCount() > 0) { - return false; - } - auto megagroup = _channel->asMegagroup(); - if (!megagroup) { - return false; - } - auto info = megagroup->mgInfo.get(); - // - // channelFull and channels_channelParticipants members count is desynced - // so we almost always have LastParticipantsCountOutdated that is set - // inside setMembersCount() and so we almost never use lastParticipants. - // - // => disable this check temporarily. - // - //if (info->lastParticipantsStatus != MegagroupInfo::LastParticipantsUpToDate) { - // _channel->updateFull(); - // return false; - //} - if (info->lastParticipants.empty()) { - return false; - } - - if (info->creator) { - _additional.creator = info->creator; - } - for_const (auto user, info->lastParticipants) { - auto admin = info->lastAdmins.find(user); - if (admin != info->lastAdmins.cend()) { - _additional.restrictedRights.erase(user); - if (admin->second.canEdit) { - _additional.adminCanEdit.emplace(user); - } else { - _additional.adminCanEdit.erase(user); - } - _additional.adminRights.emplace(user, admin->second.rights); - } else { - _additional.adminCanEdit.erase(user); - _additional.adminRights.erase(user); - auto restricted = info->lastRestricted.find(user); - if (restricted != info->lastRestricted.cend()) { - _additional.restrictedRights.emplace(user, restricted->second.rights); - } else { - _additional.restrictedRights.erase(user); - } - } - appendRow(user); - - // - // Don't count lastParticipants in _offset, because we don't know - // their exact information (admin / creator / restricted), they - // could simply be added from the last messages authors. - // - //++_offset; - } - sortByOnline(); - return true; -} - -void ParticipantsBoxController::rowClicked(not_null row) { - auto user = row->peer()->asUser(); - Expects(user != nullptr); - - if (_role == Role::Admins) { - showAdmin(user); - } else if (_role == Role::Restricted || _role == Role::Kicked) { - showRestricted(user); - } else { - _navigation->showPeerInfo(row->peer()); - } -} - -void ParticipantsBoxController::rowActionClicked(not_null row) { - auto user = row->peer()->asUser(); - Expects(user != nullptr); - - if (_role == Role::Members || _role == Role::Profile) { - kickMember(user); - } else if (_role == Role::Admins) { - removeAdmin(user); - } else if (_role == Role::Restricted) { - showRestricted(user); - } else { - removeKicked(row, user); - } -} - -bool ParticipantsBoxController::canEditAdminByRights( - not_null user) const { - if (_additional.adminRights.find(user) - != _additional.adminRights.cend()) { - return (_additional.adminCanEdit.find(user) - != _additional.adminCanEdit.cend()); - } - return (user != _additional.creator); -} - -bool ParticipantsBoxController::canEditAdmin( - not_null user) const { - if (user->isSelf()) { - return false; - } else if (_channel->amCreator()) { - return true; - } else if (!canEditAdminByRights(user)) { - return false; - } - return _channel->adminRights() & ChatAdminRight::f_add_admins; -} - -bool ParticipantsBoxController::canRestrictUser( - not_null user) const { - if (user->isSelf()) { - return false; - } else if (_channel->amCreator()) { - return true; - } else if (!canEditAdminByRights(user)) { - return false; - } - return _channel->adminRights() & ChatAdminRight::f_ban_users; -} - -base::unique_qptr ParticipantsBoxController::rowContextMenu( - QWidget *parent, - not_null row) { - Expects(row->peer()->isUser()); - - auto user = row->peer()->asUser(); - auto result = base::make_unique_q(parent); - result->addAction( - lang(lng_context_view_profile), - [weak = base::make_weak(this), user] { - if (const auto strong = weak.get()) { - strong->_navigation->showPeerInfo(user); - } - }); - if (canEditAdmin(user)) { - auto it = _additional.adminRights.find(user); - auto isCreator = (user == _additional.creator); - auto notAdmin = !isCreator && (it == _additional.adminRights.cend()); - auto label = lang(notAdmin - ? lng_context_promote_admin - : lng_context_edit_permissions); - result->addAction( - label, - [weak = base::make_weak(this), user] { - if (const auto strong = weak.get()) { - strong->showAdmin(user); - } - }); - } - if (canRestrictUser(user)) { - auto isGroup = _channel->isMegagroup(); - if (isGroup) { - result->addAction( - lang(lng_context_restrict_user), - [weak = base::make_weak(this), user]{ - if (const auto strong = weak.get()) { - strong->showRestricted(user); - } - }); - } - result->addAction( - lang(isGroup - ? lng_context_remove_from_group - : lng_profile_kick), - [weak = base::make_weak(this), user] { - if (auto strong = weak.get()) { - strong->kickMember(user); - } - }); - } - return result; -} - -void ParticipantsBoxController::showAdmin(not_null user) { - auto it = _additional.adminRights.find(user); - auto isCreator = (user == _additional.creator); - auto notAdmin = !isCreator && (it == _additional.adminRights.cend()); - auto currentRights = isCreator - ? MTP_chatAdminRights(MTP_flags(~MTPDchatAdminRights::Flag::f_add_admins | MTPDchatAdminRights::Flag::f_add_admins)) - : notAdmin ? MTP_chatAdminRights(MTP_flags(0)) : it->second; - auto weak = base::make_weak(this); - auto box = Box(_channel, user, currentRights); - auto canEdit = (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()); - auto canSave = notAdmin ? _channel->canAddAdmins() : canEdit; - if (canSave) { - box->setSaveCallback(SaveAdminCallback(_channel, user, [=]( - const MTPChatAdminRights &newRights) { - if (weak) { - weak->editAdminDone(user, newRights); - } - }, [=] { - if (weak && weak->_editBox) { - weak->_editBox->closeBox(); - } - })); - } - _editBox = Ui::show(std::move(box), LayerOption::KeepOther); -} - -void ParticipantsBoxController::editAdminDone( - not_null user, - const MTPChatAdminRights &rights) { - if (_editBox) { - _editBox->closeBox(); - } - if (_addBox) { - _addBox->closeBox(); - } - auto notAdmin = (rights.c_chatAdminRights().vflags.v == 0); - if (notAdmin) { - _additional.adminRights.erase(user); - _additional.adminPromotedBy.erase(user); - _additional.adminCanEdit.erase(user); - if (_role == Role::Admins) { - removeRow(user); - } - } else { - // It won't be replaced if the entry already exists. - _additional.adminPromotedBy.emplace(user, Auth().user()); - _additional.adminCanEdit.emplace(user); - _additional.adminRights[user] = rights; - _additional.kicked.erase(user); - _additional.restrictedRights.erase(user); - _additional.restrictedBy.erase(user); - if (_role == Role::Admins) { - prependRow(user); - } else if (_role == Role::Kicked || _role == Role::Restricted) { - removeRow(user); - } - } - recomputeTypeFor(user); - delegate()->peerListRefreshRows(); -} - -void ParticipantsBoxController::showRestricted(not_null user) { - auto it = _additional.restrictedRights.find(user); - auto restrictedRights = (it == _additional.restrictedRights.cend()) - ? MTP_chatBannedRights(MTP_flags(0), MTP_int(0)) - : it->second; - auto weak = base::make_weak(this); - auto hasAdminRights = false; - auto box = Box(_channel, user, hasAdminRights, restrictedRights); - if (_channel->canBanMembers()) { - box->setSaveCallback([megagroup = _channel.get(), user, weak](const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights) { - MTP::send(MTPchannels_EditBanned(megagroup->inputChannel, user->inputUser, newRights), rpcDone([megagroup, user, weak, oldRights, newRights](const MTPUpdates &result) { - Auth().api().applyUpdates(result); - megagroup->applyEditBanned(user, oldRights, newRights); - if (weak) { - weak->editRestrictedDone(user, newRights); - } - })); - }); - } - _editBox = Ui::show(std::move(box), LayerOption::KeepOther); -} - -void ParticipantsBoxController::editRestrictedDone(not_null user, const MTPChatBannedRights &rights) { - if (_editBox) { - _editBox->closeBox(); - } - if (_addBox) { - _addBox->closeBox(); - } - auto notBanned = (rights.c_chatBannedRights().vflags.v == 0); - auto fullBanned = rights.c_chatBannedRights().is_view_messages(); - if (notBanned) { - _additional.kicked.erase(user); - _additional.restrictedRights.erase(user); - _additional.restrictedBy.erase(user); - if (_role == Role::Kicked || _role == Role::Restricted) { - removeRow(user); - } - } else { - _additional.adminRights.erase(user); - _additional.adminCanEdit.erase(user); - _additional.adminPromotedBy.erase(user); - _additional.restrictedBy.emplace(user, Auth().user()); - if (fullBanned) { - _additional.kicked.emplace(user); - _additional.restrictedRights.erase(user); - if (_role == Role::Kicked) { - prependRow(user); - } else if (_role == Role::Admins - || _role == Role::Restricted - || _role == Role::Members) { - removeRow(user); - } - } else { - _additional.restrictedRights[user] = rights; - _additional.kicked.erase(user); - if (_role == Role::Restricted) { - prependRow(user); - } else if (_role == Role::Kicked - || _role == Role::Admins - || _role == Role::Members) { - removeRow(user); - } - } - } - recomputeTypeFor(user); - delegate()->peerListRefreshRows(); -} - -void ParticipantsBoxController::kickMember(not_null user) { - auto text = (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel)(lt_user, user->firstName); - auto weak = base::make_weak(this); - _editBox = Ui::show(Box(text, lang(lng_box_remove), [weak, user] { - if (const auto strong = weak.get()) { - strong->kickMemberSure(user); - } - }), LayerOption::KeepOther); -} - -void ParticipantsBoxController::kickMemberSure(not_null user) { - if (_editBox) { - _editBox->closeBox(); - } - auto alreadyIt = _additional.restrictedRights.find(user); - auto currentRights = (alreadyIt == _additional.restrictedRights.cend()) - ? MTP_chatBannedRights(MTP_flags(0), MTP_int(0)) - : alreadyIt->second; - - if (auto row = delegate()->peerListFindRow(user->id)) { - delegate()->peerListRemoveRow(row); - delegate()->peerListRefreshRows(); - } - Auth().api().kickParticipant(_channel, user, currentRights); -} - -void ParticipantsBoxController::removeAdmin(not_null user) { - const auto text = lng_profile_sure_remove_admin(lt_user, user->firstName); - const auto weak = base::make_weak(this); - _editBox = Ui::show(Box(text, lang(lng_box_remove), [=] { - if (const auto strong = weak.get()) { - strong->removeAdminSure(user); - } - }), LayerOption::KeepOther); -} - -void ParticipantsBoxController::removeAdminSure(not_null user) { - if (_editBox) { - _editBox->closeBox(); - } - const auto oldRightsIt = _additional.adminRights.find(user); - if (oldRightsIt == _additional.adminRights.cend()) { - return; - } - const auto weak = base::make_weak(this); - RemoveAdmin(_channel, user, oldRightsIt->second, [=] { - if (const auto strong = weak.get()) { - const auto newRights = MTP_chatAdminRights(MTP_flags(0)); - strong->editAdminDone(user, newRights); - } - }); -} - -void ParticipantsBoxController::removeKicked( - not_null row, - not_null user) { - delegate()->peerListRemoveRow(row); - delegate()->peerListRefreshRows(); - - Auth().api().unblockParticipant(_channel, user); -} - -bool ParticipantsBoxController::appendRow(not_null user) { - if (delegate()->peerListFindRow(user->id)) { - recomputeTypeFor(user); - return false; - } - delegate()->peerListAppendRow(createRow(user)); - if (_role != Role::Kicked) { - setDescriptionText(QString()); - } - return true; -} - -bool ParticipantsBoxController::prependRow(not_null user) { - if (auto row = delegate()->peerListFindRow(user->id)) { - recomputeTypeFor(user); - refreshCustomStatus(row); - if (_role == Role::Admins) { - // Perhaps we've added a new admin from search. - delegate()->peerListPrependRowFromSearchResult(row); - } - return false; - } - delegate()->peerListPrependRow(createRow(user)); - if (_role != Role::Kicked) { - setDescriptionText(QString()); - } - return true; -} - -bool ParticipantsBoxController::removeRow(not_null user) { - if (auto row = delegate()->peerListFindRow(user->id)) { - if (_role == Role::Admins) { - // Perhaps we are removing an admin from search results. - row->setCustomStatus(lang(lng_channel_admin_status_not_admin)); - delegate()->peerListConvertRowToSearchResult(row); - } else { - delegate()->peerListRemoveRow(row); - } - if (_role != Role::Kicked - && !delegate()->peerListFullRowsCount()) { - setDescriptionText(lang(lng_blocked_list_not_found)); - } - return true; - } - return false; -} - -std::unique_ptr ParticipantsBoxController::createRow( - not_null user) const { - if (_role == Role::Profile) { - return std::make_unique(user, computeType(user)); - } - auto row = std::make_unique(user); - refreshCustomStatus(row.get()); - if (_role == Role::Admins - && canEditAdminByRights(user) - && _additional.adminRights.find(user) - != _additional.adminRights.cend()) { - row->setActionLink(lang(lng_profile_kick)); - } else 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); -} - -auto ParticipantsBoxController::computeType( - not_null user) const -> Type { - auto isCreator = (user == _additional.creator); - auto isAdmin = (_additional.adminRights.find(user) != _additional.adminRights.cend()); - - auto result = Type(); - result.rights = isCreator - ? Rights::Creator - : isAdmin - ? Rights::Admin - : Rights::Normal; - result.canRemove = canRestrictUser(user); - return result; -} - -void ParticipantsBoxController::recomputeTypeFor( - not_null user) { - if (_role != Role::Profile) { - return; - } - if (auto row = delegate()->peerListFindRow(user->id)) { - static_cast(row)->setType(computeType(user)); - } -} - -void ParticipantsBoxController::refreshCustomStatus(not_null row) const { - auto user = row->peer()->asUser(); - if (_role == Role::Admins) { - auto promotedBy = _additional.adminPromotedBy.find(user); - if (promotedBy == _additional.adminPromotedBy.cend()) { - if (user == _additional.creator) { - row->setCustomStatus(lang(lng_channel_admin_status_creator)); - } else { - row->setCustomStatus(lang(lng_channel_admin_status_not_admin)); - } - } else { - row->setCustomStatus(lng_channel_admin_status_promoted_by(lt_user, App::peerName(promotedBy->second))); - } - } else if (_role == Role::Kicked || _role == Role::Restricted) { - auto restrictedBy = _additional.restrictedBy.find(user); - if (restrictedBy == _additional.restrictedBy.cend()) { - row->setCustomStatus(lng_channel_banned_status_restricted_by(lt_user, "Unknown")); - } else { - row->setCustomStatus(lng_channel_banned_status_restricted_by(lt_user, App::peerName(restrictedBy->second))); - } - } -} - -ParticipantsBoxSearchController::ParticipantsBoxSearchController(not_null channel, Role role, not_null additional) -: _channel(channel) -, _role(role) -, _additional(additional) { - _timer.setCallback([this] { searchOnServer(); }); -} - -void ParticipantsBoxSearchController::searchQuery(const QString &query) { - if (_query != query) { - _query = query; - _offset = 0; - _requestId = 0; - _allLoaded = false; - if (!_query.isEmpty() && !searchInCache()) { - _timer.callOnce(AutoSearchTimeout); - } else { - _timer.cancel(); - } - } -} - -auto ParticipantsBoxSearchController::saveState() const --> std::unique_ptr { - auto result = std::make_unique(); - result->query = _query; - result->offset = _offset; - result->allLoaded = _allLoaded; - result->wasLoading = (_requestId != 0); - return std::move(result); -} - -void ParticipantsBoxSearchController::restoreState( - std::unique_ptr state) { - if (auto my = dynamic_cast(state.get())) { - if (auto requestId = base::take(_requestId)) { - request(requestId).cancel(); - } - _cache.clear(); - _queries.clear(); - - _allLoaded = my->allLoaded; - _offset = my->offset; - _query = my->query; - if (my->wasLoading) { - searchOnServer(); - } - } -} - -void ParticipantsBoxSearchController::searchOnServer() { - Expects(!_query.isEmpty()); - loadMoreRows(); -} - -bool ParticipantsBoxSearchController::isLoading() { - return _timer.isActive() || _requestId; -} - -bool ParticipantsBoxSearchController::searchInCache() { - auto it = _cache.find(_query); - if (it != _cache.cend()) { - _requestId = 0; - searchDone(_requestId, it->second.result, it->second.requestedCount); - return true; - } - return false; -} - -bool ParticipantsBoxSearchController::loadMoreRows() { - if (_query.isEmpty()) { - return false; - } - if (!_allLoaded && !isLoading()) { - auto filter = [this] { - switch (_role) { - case Role::Admins: // Search for members, appoint as admin on found. - case Role::Profile: - 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; - auto participantsHash = 0; - - _requestId = request(MTPchannels_GetParticipants( - _channel->inputChannel, - filter, - MTP_int(_offset), - MTP_int(perPage), - MTP_int(participantsHash) - )).done([this, perPage](const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { - searchDone(requestId, result, perPage); - }).fail([this](const RPCError &error, mtpRequestId requestId) { - if (_requestId == requestId) { - _requestId = 0; - _allLoaded = true; - delegate()->peerListSearchRefreshRows(); - } - }).send(); - - auto entry = Query(); - entry.text = _query; - entry.offset = _offset; - _queries.emplace(_requestId, entry); - } - return true; -} - -void ParticipantsBoxSearchController::searchDone( - mtpRequestId requestId, - const MTPchannels_ChannelParticipants &result, - int requestedCount) { - auto query = _query; - if (requestId) { - Auth().api().parseChannelParticipants(_channel, result, [&](auto&&...) { - auto it = _queries.find(requestId); - if (it != _queries.cend()) { - query = it->second.text; - if (it->second.offset == 0) { - auto &entry = _cache[query]; - entry.result = result; - entry.requestedCount = requestedCount; - } - _queries.erase(it); - } - }); - } - - if (_requestId != requestId) { - return; - } - - _requestId = 0; - TLHelp::VisitChannelParticipants(result, base::overload([&]( - const MTPDchannels_channelParticipants &data) { - auto &list = data.vparticipants.v; - if (list.size() < requestedCount) { - // We want cache to have full information about a query with small - // results count (if we don't need the second request). So we don't - // wait for an empty results list unlike the non-search peer list. - _allLoaded = true; - } - auto parseRole = (_role == Role::Admins) ? Role::Members : _role; - for_const (auto &participant, list) { - ParticipantsBoxController::HandleParticipant(participant, parseRole, _additional, [this](not_null user) { - delegate()->peerListSearchAddRow(user); - }); - } - _offset += list.size(); - }, [&](mtpTypeId type) { - _allLoaded = true; - })); - - delegate()->peerListSearchRefreshRows(); -} - -AddParticipantBoxController::AddParticipantBoxController(not_null channel, Role role, AdminDoneCallback adminDoneCallback, BannedDoneCallback bannedDoneCallback) : PeerListController(std::make_unique(channel, &_additional)) -, _channel(channel) -, _role(role) -, _adminDoneCallback(std::move(adminDoneCallback)) -, _bannedDoneCallback(std::move(bannedDoneCallback)) { - if (_channel->mgInfo) { - _additional.creator = _channel->mgInfo->creator; - } -} - -std::unique_ptr AddParticipantBoxController::createSearchRow(not_null peer) { - if (peer->isSelf()) { - return nullptr; - } - if (auto user = peer->asUser()) { - return createRow(user); - } - return nullptr; -} - -void AddParticipantBoxController::prepare() { - delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); - auto title = [this] { - switch (_role) { - case Role::Admins: return langFactory(lng_channel_add_admin); - case Role::Restricted: return langFactory(lng_channel_add_restricted); - case Role::Kicked: return langFactory(lng_channel_add_banned); - } - Unexpected("Role in AddParticipantBoxController::prepare()"); - }; - delegate()->peerListSetTitle(title()); - setDescriptionText(lang(lng_contacts_loading)); - setSearchNoResultsText(lang(lng_blocked_list_not_found)); - delegate()->peerListRefreshRows(); - - loadMoreRows(); -} - -void AddParticipantBoxController::loadMoreRows() { - if (searchController() && searchController()->loadMoreRows()) { - return; - } - if (_loadRequestId || _allLoaded) { - return; - } - - // First query is small and fast, next loads a lot of rows. - auto perPage = (_offset > 0) ? kParticipantsPerPage : kParticipantsFirstPageCount; - auto participantsHash = 0; - - _loadRequestId = request(MTPchannels_GetParticipants( - _channel->inputChannel, - MTP_channelParticipantsRecent(), - MTP_int(_offset), - MTP_int(perPage), - MTP_int(participantsHash) - )).done([this](const MTPchannels_ChannelParticipants &result) { - _loadRequestId = 0; - - Auth().api().parseChannelParticipants(_channel, result, [&]( - int availableCount, - const QVector &list) { - for (auto &participant : list) { - HandleParticipant( - participant, - &_additional, - [this](auto user) { appendRow(user); }); - } - if (auto size = list.size()) { - _offset += size; - } else { - // To be sure - wait for a whole empty result list. - _allLoaded = true; - } - }); - - if (delegate()->peerListFullRowsCount() > 0) { - setDescriptionText(QString()); - } else if (_allLoaded) { - setDescriptionText(lang(lng_blocked_list_not_found)); - } - delegate()->peerListRefreshRows(); - }).fail([this](const RPCError &error) { - _loadRequestId = 0; - }).send(); -} - -void AddParticipantBoxController::rowClicked(not_null row) { - auto user = row->peer()->asUser(); - switch (_role) { - case Role::Admins: return showAdmin(user); - case Role::Restricted: return showRestricted(user); - case Role::Kicked: return kickUser(user); - } - Unexpected("Role in AddParticipantBoxController::rowClicked()"); -} - -template -bool AddParticipantBoxController::checkInfoLoaded(not_null user, Callback callback) { - if (_additional.infoNotLoaded.find(user) == _additional.infoNotLoaded.end()) { - return true; - } - - // We don't know what this user status is in the group. - request(MTPchannels_GetParticipant(_channel->inputChannel, user->inputUser)).done([this, user, callback](const MTPchannels_ChannelParticipant &result) { - Expects(result.type() == mtpc_channels_channelParticipant); - auto &participant = result.c_channels_channelParticipant(); - App::feedUsers(participant.vusers); - HandleParticipant(participant.vparticipant, &_additional, [](not_null) {}); - _additional.infoNotLoaded.erase(user); - callback(); - }).fail([this, user, callback](const RPCError &error) { - _additional.infoNotLoaded.erase(user); - _additional.external.emplace(user); - callback(); - }).send(); - return false; -} - -void AddParticipantBoxController::showAdmin(not_null user, bool sure) { - if (!checkInfoLoaded(user, [this, user] { showAdmin(user); })) { - return; - } - - if (sure && _editBox) { - // Close the confirmation box. - _editBox->closeBox(); - } - - // Check restrictions. - auto weak = base::make_weak(this); - auto alreadyIt = _additional.adminRights.find(user); - auto currentRights = (_additional.creator == user) - ? MTP_chatAdminRights(MTP_flags(~MTPDchatAdminRights::Flag::f_add_admins | MTPDchatAdminRights::Flag::f_add_admins)) - : MTP_chatAdminRights(MTP_flags(0)); - if (alreadyIt != _additional.adminRights.end()) { - // The user is already an admin. - currentRights = alreadyIt->second; - } else if (_additional.kicked.find(user) != _additional.kicked.end()) { - // The user is banned. - if (_channel->canAddMembers()) { - if (_channel->canBanMembers()) { - if (!sure) { - _editBox = Ui::show(Box(lang(lng_sure_add_admin_unban), [weak, user] { - if (weak) { - weak->showAdmin(user, true); - } - }), LayerOption::KeepOther); - return; - } - } else { - Ui::show(Box( - lang(lng_error_cant_add_admin_unban)), - LayerOption::KeepOther); - return; - } - } else { - Ui::show(Box( - lang(lng_error_cant_add_admin_invite)), - LayerOption::KeepOther); - return; - } - } else if (_additional.restrictedRights.find(user) != _additional.restrictedRights.end()) { - // The user is restricted. - if (_channel->canBanMembers()) { - if (!sure) { - _editBox = Ui::show(Box(lang(lng_sure_add_admin_unban), [weak, user] { - if (weak) { - weak->showAdmin(user, true); - } - }), LayerOption::KeepOther); - return; - } - } else { - Ui::show(Box( - lang(lng_error_cant_add_admin_unban)), - LayerOption::KeepOther); - return; - } - } else if (_additional.external.find(user) != _additional.external.end()) { - // The user is not in the group yet. - if (_channel->canAddMembers()) { - if (!sure) { - const auto text = lang(_channel->isMegagroup() - ? lng_sure_add_admin_invite - : lng_sure_add_admin_invite_channel); - _editBox = Ui::show(Box(text, [weak, user] { - if (weak) { - weak->showAdmin(user, true); - } - }), LayerOption::KeepOther); - return; - } - } else { - Ui::show( - Box(lang(lng_error_cant_add_admin_invite)), - LayerOption::KeepOther); - return; - } - } - - // Finally show the admin. - auto canNotEdit = (_additional.creator == user) - || ((alreadyIt != _additional.adminRights.end()) - && (_additional.adminCanEdit.find(user) == _additional.adminCanEdit.end())); - auto box = Box(_channel, user, currentRights); - if (!canNotEdit) { - box->setSaveCallback(SaveAdminCallback(_channel, user, [=]( - const MTPChatAdminRights &newRights) { - if (weak) { - weak->editAdminDone(user, newRights); - } - }, [=] { - if (weak && weak->_editBox) { - weak->_editBox->closeBox(); - } - })); - } - _editBox = Ui::show(std::move(box), LayerOption::KeepOther); -} - -void AddParticipantBoxController::editAdminDone( - not_null user, - const MTPChatAdminRights &rights) { - if (_editBox) _editBox->closeBox(); - _additional.restrictedRights.erase(user); - _additional.restrictedBy.erase(user); - _additional.kicked.erase(user); - _additional.external.erase(user); - if (rights.c_chatAdminRights().vflags.v == 0) { - _additional.adminRights.erase(user); - _additional.adminPromotedBy.erase(user); - _additional.adminCanEdit.erase(user); - } else { - _additional.adminRights[user] = rights; - _additional.adminCanEdit.emplace(user); - auto it = _additional.adminPromotedBy.find(user); - if (it == _additional.adminPromotedBy.end()) { - _additional.adminPromotedBy.emplace(user, Auth().user()); - } - } - if (_adminDoneCallback) { - _adminDoneCallback(user, rights); - } -} - -void AddParticipantBoxController::showRestricted(not_null user, bool sure) { - if (!checkInfoLoaded(user, [this, user] { showRestricted(user); })) { - return; - } - - if (sure && _editBox) { - // Close the confirmation box. - _editBox->closeBox(); - } - - // Check restrictions. - auto weak = base::make_weak(this); - auto alreadyIt = _additional.restrictedRights.find(user); - auto currentRights = MTP_chatBannedRights(MTP_flags(0), MTP_int(0)); - auto hasAdminRights = false; - if (alreadyIt != _additional.restrictedRights.end()) { - // The user is already banned or restricted. - currentRights = alreadyIt->second; - } else if (_additional.adminRights.find(user) != _additional.adminRights.end() || _additional.creator == user) { - // The user is an admin or creator. - if (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()) { - hasAdminRights = true; - if (!sure) { - _editBox = Ui::show(Box(lang(lng_sure_ban_admin), [weak, user] { - if (weak) { - weak->showRestricted(user, true); - } - }), LayerOption::KeepOther); - return; - } - } else { - Ui::show( - Box(lang(lng_error_cant_ban_admin)), - LayerOption::KeepOther); - return; - } - } - - // Finally edit the restricted. - auto box = Box(_channel, user, hasAdminRights, currentRights); - box->setSaveCallback([user, weak](const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights) { - if (weak) { - weak->restrictUserSure(user, oldRights, newRights); - } - }); - _editBox = Ui::show(std::move(box), LayerOption::KeepOther); -} - -void AddParticipantBoxController::restrictUserSure(not_null user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights) { - auto weak = base::make_weak(this); - MTP::send(MTPchannels_EditBanned(_channel->inputChannel, user->inputUser, newRights), rpcDone([megagroup = _channel.get(), user, weak, oldRights, newRights](const MTPUpdates &result) { - Auth().api().applyUpdates(result); - megagroup->applyEditBanned(user, oldRights, newRights); - if (const auto strong = weak.get()) { - strong->editRestrictedDone(user, newRights); - } - })); -} - -void AddParticipantBoxController::editRestrictedDone( - not_null user, - const MTPChatBannedRights &rights) { - if (_editBox) _editBox->closeBox(); - _additional.adminRights.erase(user); - _additional.adminCanEdit.erase(user); - _additional.adminPromotedBy.erase(user); - if (rights.c_chatBannedRights().vflags.v == 0) { - _additional.restrictedRights.erase(user); - _additional.restrictedBy.erase(user); - _additional.kicked.erase(user); - } else { - _additional.restrictedRights[user] = rights; - if (rights.c_chatBannedRights().vflags.v & MTPDchatBannedRights::Flag::f_view_messages) { - _additional.kicked.emplace(user); - } else { - _additional.kicked.erase(user); - } - _additional.restrictedBy.emplace(user, Auth().user()); - } - if (_bannedDoneCallback) { - _bannedDoneCallback(user, rights); - } -} - -void AddParticipantBoxController::kickUser(not_null user, bool sure) { - if (!checkInfoLoaded(user, [this, user] { kickUser(user); })) { - return; - } - - // Check restrictions. - auto weak = base::make_weak(this); - if (_additional.adminRights.find(user) != _additional.adminRights.end() || _additional.creator == user) { - // The user is an admin or creator. - if (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()) { - if (!sure) { - _editBox = Ui::show(Box(lang(lng_sure_ban_admin), [weak, user] { - if (weak) { - weak->kickUser(user, true); - } - }), LayerOption::KeepOther); - return; - } - } else { - Ui::show( - Box(lang(lng_error_cant_ban_admin)), - LayerOption::KeepOther); - return; - } - } - - // Finally kick him. - if (!sure) { - const auto text = (_channel->isMegagroup() - ? lng_sure_ban_user_group - : lng_sure_ban_user_channel)(lt_user, App::peerName(user)); - _editBox = Ui::show(Box(text, [=] { - if (weak) { - weak->kickUser(user, true); - } - }), LayerOption::KeepOther); - return; - } - auto currentRights = MTP_chatBannedRights(MTP_flags(0), MTP_int(0)); - auto alreadyIt = _additional.restrictedRights.find(user); - if (alreadyIt != _additional.restrictedRights.end()) { - // The user is already banned or restricted. - currentRights = alreadyIt->second; - } - restrictUserSure(user, currentRights, ChannelData::KickedRestrictedRights()); -} - -bool AddParticipantBoxController::appendRow(not_null user) { - if (delegate()->peerListFindRow(user->id) || user->isSelf()) { - return false; - } - delegate()->peerListAppendRow(createRow(user)); - return true; -} - -bool AddParticipantBoxController::prependRow(not_null user) { - if (delegate()->peerListFindRow(user->id)) { - return false; - } - delegate()->peerListPrependRow(createRow(user)); - return true; -} - -std::unique_ptr AddParticipantBoxController::createRow(not_null user) const { - return std::make_unique(user); -} - -template -void AddParticipantBoxController::HandleParticipant( - const MTPChannelParticipant &participant, - not_null additional, - Callback callback) { - switch (participant.type()) { - case mtpc_channelParticipantAdmin: { - auto &admin = participant.c_channelParticipantAdmin(); - if (auto user = App::userLoaded(admin.vuser_id.v)) { - additional->infoNotLoaded.erase(user); - additional->restrictedRights.erase(user); - additional->kicked.erase(user); - additional->restrictedBy.erase(user); - additional->adminRights[user] = admin.vadmin_rights; - if (admin.is_can_edit()) { - additional->adminCanEdit.emplace(user); - } else { - additional->adminCanEdit.erase(user); - } - if (auto promoted = App::userLoaded(admin.vpromoted_by.v)) { - auto it = additional->adminPromotedBy.find(user); - if (it == additional->adminPromotedBy.end()) { - additional->adminPromotedBy.emplace(user, promoted); - } else { - it->second = promoted; - } - } else { - LOG(("API Error: No user %1 for admin promoted by.").arg(admin.vpromoted_by.v)); - } - callback(user); - } - } break; - case mtpc_channelParticipantCreator: { - auto &creator = participant.c_channelParticipantCreator(); - if (auto user = App::userLoaded(creator.vuser_id.v)) { - additional->infoNotLoaded.erase(user); - additional->creator = user; - callback(user); - } - } break; - case mtpc_channelParticipantBanned: { - auto &banned = participant.c_channelParticipantBanned(); - if (auto user = App::userLoaded(banned.vuser_id.v)) { - additional->infoNotLoaded.erase(user); - additional->adminRights.erase(user); - additional->adminCanEdit.erase(user); - additional->adminPromotedBy.erase(user); - if (banned.is_left()) { - additional->kicked.emplace(user); - } else { - additional->kicked.erase(user); - } - additional->restrictedRights[user] = banned.vbanned_rights; - if (auto kickedby = App::userLoaded(banned.vkicked_by.v)) { - auto it = additional->restrictedBy.find(user); - if (it == additional->restrictedBy.end()) { - additional->restrictedBy.emplace(user, kickedby); - } else { - it->second = kickedby; - } - } - callback(user); - } - } break; - case mtpc_channelParticipant: { - auto &data = participant.c_channelParticipant(); - if (auto user = App::userLoaded(data.vuser_id.v)) { - additional->infoNotLoaded.erase(user); - additional->adminRights.erase(user); - additional->adminCanEdit.erase(user); - additional->adminPromotedBy.erase(user); - additional->restrictedRights.erase(user); - additional->kicked.erase(user); - additional->restrictedBy.erase(user); - callback(user); - } - } break; - default: Unexpected("Participant type in AddParticipantBoxController::HandleParticipant()"); - } -} - -AddParticipantBoxSearchController::AddParticipantBoxSearchController(not_null channel, not_null additional) -: _channel(channel) -, _additional(additional) { - _timer.setCallback([this] { searchOnServer(); }); -} - -void AddParticipantBoxSearchController::searchQuery(const QString &query) { - if (_query != query) { - _query = query; - _offset = 0; - _requestId = 0; - _participantsLoaded = false; - _chatsContactsAdded = false; - _globalLoaded = false; - if (!_query.isEmpty() && !searchParticipantsInCache()) { - _timer.callOnce(AutoSearchTimeout); - } else { - _timer.cancel(); - } - } -} - -void AddParticipantBoxSearchController::searchOnServer() { - Expects(!_query.isEmpty()); - loadMoreRows(); -} - -bool AddParticipantBoxSearchController::isLoading() { - return _timer.isActive() || _requestId; -} - -bool AddParticipantBoxSearchController::searchParticipantsInCache() { - auto it = _participantsCache.find(_query); - if (it != _participantsCache.cend()) { - _requestId = 0; - searchParticipantsDone(_requestId, it->second.result, it->second.requestedCount); - return true; - } - return false; -} - -bool AddParticipantBoxSearchController::searchGlobalInCache() { - auto it = _globalCache.find(_query); - if (it != _globalCache.cend()) { - _requestId = 0; - searchGlobalDone(_requestId, it->second); - return true; - } - return false; -} - -bool AddParticipantBoxSearchController::loadMoreRows() { - if (_query.isEmpty()) { - return false; - } - if (_globalLoaded) { - return true; - } - if (_participantsLoaded) { - if (!_chatsContactsAdded) { - addChatsContacts(); - } - if (!isLoading() && !searchGlobalInCache()) { - requestGlobal(); - } - } else if (!isLoading()) { - requestParticipants(); - } - return true; -} - -void AddParticipantBoxSearchController::requestParticipants() { - // 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; - auto participantsHash = 0; - - _requestId = request(MTPchannels_GetParticipants( - _channel->inputChannel, - MTP_channelParticipantsSearch(MTP_string(_query)), - MTP_int(_offset), - MTP_int(perPage), - MTP_int(participantsHash) - )).done([this, perPage](const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { - searchParticipantsDone(requestId, result, perPage); - }).fail([this](const RPCError &error, mtpRequestId requestId) { - if (_requestId == requestId) { - _requestId = 0; - _participantsLoaded = true; - loadMoreRows(); - delegate()->peerListSearchRefreshRows(); - } - }).send(); - auto entry = Query(); - entry.text = _query; - entry.offset = _offset; - _participantsQueries.emplace(_requestId, entry); -} - -void AddParticipantBoxSearchController::searchParticipantsDone(mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount) { - auto query = _query; - if (requestId) { - Auth().api().parseChannelParticipants(_channel, result, [&](auto&&...) { - auto it = _participantsQueries.find(requestId); - if (it != _participantsQueries.cend()) { - query = it->second.text; - if (it->second.offset == 0) { - auto &entry = _participantsCache[query]; - entry.result = result; - entry.requestedCount = requestedCount; - } - _participantsQueries.erase(it); - } - }); - } - - if (_requestId != requestId) { - return; - } - _requestId = 0; - TLHelp::VisitChannelParticipants(result, base::overload([&]( - const MTPDchannels_channelParticipants &data) { - auto &list = data.vparticipants.v; - if (list.size() < requestedCount) { - // We want cache to have full information about a query with small - // results count (if we don't need the second request). So we don't - // wait for an empty results list unlike the non-search peer list. - _participantsLoaded = true; - if (list.empty() && _offset == 0) { - // No results, so we want to request global search immediately. - loadMoreRows(); - } - } - auto addUser = [&](auto user) { - delegate()->peerListSearchAddRow(user); - }; - for (auto &participant : list) { - AddParticipantBoxController::HandleParticipant( - participant, - _additional, - addUser); - } - _offset += list.size(); - }, [&](mtpTypeId type) { - _participantsLoaded = true; - })); - - delegate()->peerListSearchRefreshRows(); -} - -void AddParticipantBoxSearchController::requestGlobal() { - if (_query.isEmpty()) { - _globalLoaded = true; - return; - } - - auto perPage = SearchPeopleLimit; - _requestId = request(MTPcontacts_Search( - MTP_string(_query), - MTP_int(perPage) - )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { - searchGlobalDone(requestId, result); - }).fail([=](const RPCError &error, mtpRequestId requestId) { - if (_requestId == requestId) { - _requestId = 0; - _globalLoaded = true; - delegate()->peerListSearchRefreshRows(); - } - }).send(); - _globalQueries.emplace(_requestId, _query); -} - -void AddParticipantBoxSearchController::searchGlobalDone(mtpRequestId requestId, const MTPcontacts_Found &result) { - Expects(result.type() == mtpc_contacts_found); - - auto &found = result.c_contacts_found(); - auto query = _query; - if (requestId) { - App::feedUsers(found.vusers); - App::feedChats(found.vchats); - auto it = _globalQueries.find(requestId); - if (it != _globalQueries.cend()) { - query = it->second; - _globalCache[query] = result; - _globalQueries.erase(it); - } - } - - const auto feedList = [&](const MTPVector &list) { - const auto contains = [](const auto &map, const auto &value) { - return map.find(value) != map.end(); - }; - for (const auto &mtpPeer : list.v) { - const auto peerId = peerFromMTP(mtpPeer); - if (const auto peer = App::peerLoaded(peerId)) { - if (const auto user = peer->asUser()) { - if (_additional->creator != user - && !contains(_additional->adminRights, user) - && !contains(_additional->restrictedRights, user) - && !contains(_additional->external, user) - && !contains(_additional->kicked, user)) { - _additional->infoNotLoaded.emplace(user); - } - delegate()->peerListSearchAddRow(user); - } - } - } - }; - if (_requestId == requestId) { - _requestId = 0; - _globalLoaded = true; - feedList(found.vmy_results); - feedList(found.vresults); - delegate()->peerListSearchRefreshRows(); - } -} - -void AddParticipantBoxSearchController::addChatsContacts() { - _chatsContactsAdded = true; - - auto wordList = TextUtilities::PrepareSearchWords(_query); - if (wordList.empty()) { - return; - } - - auto getSmallestIndex = [&](Dialogs::IndexedList *list) -> const Dialogs::List* { - if (list->isEmpty()) { - return nullptr; - } - - auto result = (const Dialogs::List*)nullptr; - for_const (auto &word, wordList) { - auto found = list->filtered(word[0]); - if (found->isEmpty()) { - return nullptr; - } - if (!result || result->size() > found->size()) { - result = found; - } - } - return result; - }; - auto dialogsIndex = getSmallestIndex(App::main()->dialogsList()); - auto contactsIndex = getSmallestIndex(App::main()->contactsNoDialogsList()); - - auto allWordsAreFound = [&](const base::flat_set &nameWords) { - auto hasNamePartStartingWith = [&](const QString &word) { - for (auto &nameWord : nameWords) { - if (nameWord.startsWith(word)) { - return true; - } - } - return false; - }; - - for_const (auto &word, wordList) { - if (!hasNamePartStartingWith(word)) { - return false; - } - } - return true; - }; - auto filterAndAppend = [&](const Dialogs::List *list) { - if (!list) { - return; - } - - for (const auto row : *list) { - if (const auto history = row->history()) { - if (const auto user = history->peer->asUser()) { - if (allWordsAreFound(user->nameWords())) { - delegate()->peerListSearchAddRow(user); - } - } - } - } - }; - filterAndAppend(dialogsIndex); - filterAndAppend(contactsIndex); - delegate()->peerListSearchRefreshRows(); -} - -} // namespace Profile diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 6ac1107bc..a9570aabe 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/add_contact_box.h" #include "boxes/report_box.h" #include "boxes/create_poll_box.h" -#include "boxes/peer_list_controllers.h" +#include "boxes/peers/add_participants_box.h" #include "boxes/peers/manage_peer_box.h" #include "boxes/peers/edit_peer_info_box.h" #include "ui/toast/toast.h" @@ -368,7 +368,7 @@ void Filler::addChatActions(not_null chat) { lang(lng_profile_add_participant), [chat] { AddChatMembers(chat); }); } - if (chat->canWrite()) { + if (chat->canSendPolls()) { _addAction( lang(lng_polls_create), [=] { PeerMenuCreatePoll(chat); }); @@ -410,7 +410,7 @@ void Filler::addChannelActions(not_null channel) { lang(lng_channel_add_members), [channel] { PeerMenuAddChannelMembers(channel); }); } - if (channel->canWrite()) { + if (channel->canSendPolls()) { _addAction( lang(lng_polls_create), [=] { PeerMenuCreatePoll(channel); }); diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 7902e2bcf..ee488d243 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -1,5 +1,9 @@ +<(src_loc)/boxes/peers/add_participants_box.cpp +<(src_loc)/boxes/peers/add_participants_box.h <(src_loc)/boxes/peers/edit_participant_box.cpp <(src_loc)/boxes/peers/edit_participant_box.h +<(src_loc)/boxes/peers/edit_participants_box.cpp +<(src_loc)/boxes/peers/edit_participants_box.h <(src_loc)/boxes/peers/edit_peer_info_box.cpp <(src_loc)/boxes/peers/edit_peer_info_box.h <(src_loc)/boxes/peers/edit_peer_permissions_box.cpp @@ -584,8 +588,6 @@ <(src_loc)/profile/profile_block_peer_list.h <(src_loc)/profile/profile_block_widget.cpp <(src_loc)/profile/profile_block_widget.h -<(src_loc)/profile/profile_channel_controllers.cpp -<(src_loc)/profile/profile_channel_controllers.h <(src_loc)/profile/profile_cover_drop_area.cpp <(src_loc)/profile/profile_cover_drop_area.h <(src_loc)/settings/settings_advanced.cpp