From f7359093b4b4615c8d077b688144a28591b48668 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 14 Aug 2017 15:53:49 +0300 Subject: [PATCH] Replace ContactsBox with PeerListBox in two cases. - View contacts list in PeerListBox. - Add participants when creating group / channel in PeerListBox. --- Telegram/SourceFiles/base/qthelp_regex.h | 2 +- .../SourceFiles/boxes/add_contact_box.cpp | 386 +++++++++++++++--- Telegram/SourceFiles/boxes/add_contact_box.h | 23 +- Telegram/SourceFiles/boxes/contacts_box.cpp | 9 +- Telegram/SourceFiles/boxes/contacts_box.h | 13 - .../SourceFiles/boxes/edit_privacy_box.cpp | 4 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 209 +--------- Telegram/SourceFiles/boxes/peer_list_box.h | 103 ++--- .../boxes/peer_list_controllers.cpp | 270 ++++++++++++ .../SourceFiles/boxes/peer_list_controllers.h | 119 ++++++ Telegram/SourceFiles/boxes/share_box.cpp | 2 +- Telegram/SourceFiles/core/utils.h | 2 +- .../dialogs/dialogs_inner_widget.h | 2 +- Telegram/SourceFiles/history/history.h | 2 +- Telegram/SourceFiles/history/history_item.h | 2 +- Telegram/SourceFiles/history/history_widget.h | 2 +- Telegram/SourceFiles/observer_peer.h | 2 +- .../platform/mac/main_window_mac.mm | 6 +- .../platform/win/main_window_win.h | 2 +- .../profile/profile_channel_controllers.cpp | 14 +- .../SourceFiles/profile/profile_cover.cpp | 10 +- .../settings/settings_privacy_controllers.cpp | 7 +- .../settings/settings_privacy_widget.cpp | 2 +- Telegram/SourceFiles/storage/localstorage.cpp | 2 +- Telegram/SourceFiles/ui/abstract_button.h | 2 +- Telegram/SourceFiles/ui/images.h | 4 +- Telegram/SourceFiles/ui/text/text.h | 2 +- Telegram/SourceFiles/ui/twidget.h | 2 +- .../SourceFiles/window/window_controller.h | 2 +- .../SourceFiles/window/window_main_menu.cpp | 9 +- Telegram/gyp/telegram_sources.txt | 2 + 31 files changed, 825 insertions(+), 393 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/peer_list_controllers.cpp create mode 100644 Telegram/SourceFiles/boxes/peer_list_controllers.h diff --git a/Telegram/SourceFiles/base/qthelp_regex.h b/Telegram/SourceFiles/base/qthelp_regex.h index ceb0d3b633..582b527868 100644 --- a/Telegram/SourceFiles/base/qthelp_regex.h +++ b/Telegram/SourceFiles/base/qthelp_regex.h @@ -65,7 +65,7 @@ enum class RegExOption { DontAutomaticallyOptimize = QRegularExpression::DontAutomaticallyOptimizeOption, #endif // OS_MAC_OLD }; -Q_DECLARE_FLAGS(RegExOptions, RegExOption); +using RegExOptions = QFlags; Q_DECLARE_OPERATORS_FOR_FLAGS(RegExOptions); inline RegularExpressionMatch regex_match(const QString &string, const QString &subject, RegExOptions options = 0) { diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index ffba58ccb5..0088c0685c 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -25,9 +25,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "lang/lang_keys.h" #include "messenger.h" #include "mtproto/sender.h" -#include "boxes/contacts_box.h" +#include "base/flat_set.h" #include "boxes/confirm_box.h" #include "boxes/photo_crop_box.h" +#include "boxes/peer_list_controllers.h" #include "core/file_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" @@ -53,8 +54,203 @@ style::InputField CreateBioFieldStyle() { return result; } +base::flat_set> GetAlreadyInFromPeer(PeerData *peer) { + if (!peer) { + return {}; + } + if (auto chat = peer->asChat()) { + auto participants = chat->participants.keys(); + return { participants.cbegin(), participants.cend() }; + } else if (auto channel = peer->asChannel()) { + if (channel->isMegagroup()) { + auto &participants = channel->mgInfo->lastParticipants; + return { participants.cbegin(), participants.cend() }; + } + } + return {}; +} + +class AddParticipantsBoxController : public ContactsBoxController { +public: + AddParticipantsBoxController(PeerData *peer); + AddParticipantsBoxController( + gsl::not_null channel, + base::flat_set> &&alreadyIn); + + using ContactsBoxController::ContactsBoxController; + + void rowClicked(gsl::not_null row) override; + void itemDeselectedHook(gsl::not_null peer) override; + +protected: + void prepareViewHook() override; + std::unique_ptr createRow(gsl::not_null user) override; + +private: + int alreadyInCount() const; + bool isAlreadyIn(gsl::not_null user) const; + int fullCount() const; + void updateTitle(); + + PeerData *_peer = nullptr; + base::flat_set> _alreadyIn; + +}; + +AddParticipantsBoxController::AddParticipantsBoxController(PeerData *peer) +: ContactsBoxController(std::make_unique()) +, _peer(peer) +, _alreadyIn(GetAlreadyInFromPeer(peer)) { +} + +AddParticipantsBoxController::AddParticipantsBoxController( + gsl::not_null channel, + base::flat_set> &&alreadyIn) +: ContactsBoxController(std::make_unique()) +, _peer(channel) +, _alreadyIn(std::move(alreadyIn)) { +} + +void AddParticipantsBoxController::rowClicked(gsl::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()), KeepOtherLayers); + } + } else if (count >= Global::ChatSizeMax() && count < Global::MegagroupSizeMax()) { + Ui::show(Box(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers); + } +} + +void AddParticipantsBoxController::itemDeselectedHook(gsl::not_null peer) { + updateTitle(); +} + +void AddParticipantsBoxController::prepareViewHook() { + updateTitle(); +} + +int AddParticipantsBoxController::alreadyInCount() const { + return _alreadyIn.empty() ? 1 : _alreadyIn.size(); // self +} + +bool AddParticipantsBoxController::isAlreadyIn(gsl::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() && channel->mgInfo->lastParticipants.contains(user)); + } + Unexpected("User in AddParticipantsBoxController::isAlreadyIn"); +} + +int AddParticipantsBoxController::fullCount() const { + return alreadyInCount() + delegate()->peerListSelectedRowsCount(); +} + +std::unique_ptr AddParticipantsBoxController::createRow(gsl::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; }); +} + } // namespace +QString PeerFloodErrorText(PeerFloodType type) { + auto link = textcmdLink( + Messenger::Instance().createInternalLinkFull(qsl("spambot")), + lang(lng_cant_more_info)); + if (type == PeerFloodType::InviteGroup) { + return lng_cant_invite_not_contact(lt_more_info, link); + } + return lng_cant_send_to_not_contact(lt_more_info, link); +} + +void ShowAddContactsToChatBox(gsl::not_null chat) { + auto initBox = [chat](gsl::not_null box) { + box->addButton(langFactory(lng_participant_invite), [box, chat] { + auto rows = box->peerListCollectSelectedRows(); + if (!rows.empty()) { + auto users = std::vector>(); + for (auto peer : rows) { + auto user = peer->asUser(); + t_assert(user != nullptr); + t_assert(!user->isSelf()); + users.push_back(peer->asUser()); + } + App::main()->addParticipants(chat, users); + Ui::showPeerHistory(chat, ShowAtTheEndMsgId); + } + }); + box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); + }; + Ui::show(Box(std::make_unique(chat), std::move(initBox))); +} + +void ShowAddContactsToChannelBox( + gsl::not_null channel, + base::flat_set> &&alreadyIn, + bool justCreated) { + auto initBox = [channel, justCreated](gsl::not_null box) { + auto subscription = std::make_shared(); + box->addButton(langFactory(lng_participant_invite), [box, channel, subscription] { + auto rows = box->peerListCollectSelectedRows(); + if (!rows.empty()) { + auto users = std::vector>(); + for (auto peer : rows) { + auto user = peer->asUser(); + t_assert(user != nullptr); + t_assert(!user->isSelf()); + users.push_back(peer->asUser()); + } + App::main()->addParticipants(channel, users); + 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) { + *subscription = box->boxClosing.add_subscription([channel] { + Ui::showPeerHistory(channel, ShowAtTheEndMsgId); + }); + } + }; + Ui::show(Box(std::make_unique(channel, std::move(alreadyIn)), std::move(initBox))); +} + +void ShowAddContactsToChannelBox( + gsl::not_null channel, + base::flat_set> &&alreadyIn) { + ShowAddContactsToChannelBox(channel, std::move(alreadyIn), false); +} + +void ShowAddContactsToChannelBox(gsl::not_null channel) { + ShowAddContactsToChannelBox(channel, {}, true); +} + class RevokePublicLinkBox::Inner : public TWidget, private MTP::Sender { public: Inner(QWidget *parent, base::lambda revokeCallback); @@ -379,6 +575,68 @@ void GroupInfoBox::onNameSubmit() { } } +void GroupInfoBox::createGroup(gsl::not_null selectUsersBox, const QString &title, const std::vector> &users) { + if (_creationRequestId) return; + + auto inputs = QVector(); + inputs.reserve(users.size()); + for (auto peer : users) { + auto user = peer->asUser(); + t_assert(user != nullptr); + if (!user->isSelf()) { + inputs.push_back(user->inputUser); + } + } + _creationRequestId = request(MTPmessages_CreateChat(MTP_vector(inputs), MTP_string(title))).done([this](const MTPUpdates &result) { + Ui::hideLayer(); + + App::main()->sentUpdatesReceived(result); + + auto success = base::make_optional(&result) + | [](auto updates) -> base::optional*> { + switch (updates->type()) { + case mtpc_updates: + return &updates->c_updates().vchats.v; + case mtpc_updatesCombined: + return &updates->c_updatesCombined().vchats.v; + } + LOG(("API Error: unexpected update cons %1 (GroupInfoBox::creationDone)").arg(updates->type())); + return base::none; + } + | [](auto chats) { + return (!chats->empty() && chats->front().type() == mtpc_chat) + ? base::make_optional(chats) + : base::none; + } + | [](auto chats) { + return App::chat(chats->front().c_chat().vid.v); + } + | [this](gsl::not_null chat) { + if (!_photoImage.isNull()) { + Messenger::Instance().uploadProfilePhoto(_photoImage, chat->id); + } + Ui::showPeerHistory(chat, ShowAtUnreadMsgId); + }; + if (!success) { + LOG(("API Error: chat not found in updates (ContactsBox::creationDone)")); + } + }).fail([this, selectUsersBox](const RPCError &error) { + _creationRequestId = 0; + if (error.type() == qstr("NO_CHAT_TITLE")) { + auto guard = weak(this); + selectUsersBox->closeBox(); + if (guard) { + _title->showError(); + } + } else if (error.type() == qstr("USERS_TOO_FEW")) { + } else if (error.type() == qstr("PEER_FLOOD")) { + Ui::show(Box(PeerFloodErrorText(PeerFloodType::InviteGroup)), KeepOtherLayers); + } else if (error.type() == qstr("USER_RESTRICTED")) { + Ui::show(Box(lang(lng_cant_do_this)), KeepOtherLayers); + } + }).send(); +} + void GroupInfoBox::onNext() { if (_creationRequestId) return; @@ -389,64 +647,82 @@ void GroupInfoBox::onNext() { _title->showError(); return; } - if (_creating == CreatingGroupGroup) { - Ui::show(Box(title, _photoImage), KeepOtherLayers); + if (_creating != CreatingGroupGroup) { + createChannel(title, description); } else { - bool mega = false; - auto flags = mega ? MTPchannels_CreateChannel::Flag::f_megagroup : MTPchannels_CreateChannel::Flag::f_broadcast; - _creationRequestId = MTP::send(MTPchannels_CreateChannel(MTP_flags(flags), MTP_string(title), MTP_string(description)), rpcDone(&GroupInfoBox::creationDone), rpcFail(&GroupInfoBox::creationFail)); + auto initBox = [title, weak = weak(this)](gsl::not_null box) { + box->addButton(langFactory(lng_create_group_create), [box, title, weak] { + if (weak) { + auto rows = box->peerListCollectSelectedRows(); + if (!rows.empty()) { + weak->createGroup(box, title, rows); + } + } + }); + box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); + }; + Ui::show(Box(std::make_unique(nullptr), std::move(initBox)), KeepOtherLayers); } } -void GroupInfoBox::creationDone(const MTPUpdates &updates) { - App::main()->sentUpdatesReceived(updates); +void GroupInfoBox::createChannel(const QString &title, const QString &description) { + bool mega = false; + auto flags = mega ? MTPchannels_CreateChannel::Flag::f_megagroup : MTPchannels_CreateChannel::Flag::f_broadcast; + _creationRequestId = request(MTPchannels_CreateChannel(MTP_flags(flags), MTP_string(title), MTP_string(description))).done([this](const MTPUpdates &result) { + App::main()->sentUpdatesReceived(result); - const QVector *v = 0; - switch (updates.type()) { - case mtpc_updates: v = &updates.c_updates().vchats.v; break; - case mtpc_updatesCombined: v = &updates.c_updatesCombined().vchats.v; break; - default: LOG(("API Error: unexpected update cons %1 (GroupInfoBox::creationDone)").arg(updates.type())); break; - } - - ChannelData *channel = 0; - if (v && !v->isEmpty() && v->front().type() == mtpc_channel) { - channel = App::channel(v->front().c_channel().vid.v); - if (channel) { - if (!_photoImage.isNull()) { - Messenger::Instance().uploadProfilePhoto(_photoImage, channel->id); + auto success = base::make_optional(&result) + | [](auto updates) -> base::optional*> { + switch (updates->type()) { + case mtpc_updates: + return &updates->c_updates().vchats.v; + case mtpc_updatesCombined: + return &updates->c_updatesCombined().vchats.v; + } + LOG(("API Error: unexpected update cons %1 (GroupInfoBox::createChannel)").arg(updates->type())); + return base::none; } - _createdChannel = channel; - _creationRequestId = MTP::send(MTPchannels_ExportInvite(_createdChannel->inputChannel), rpcDone(&GroupInfoBox::exportDone)); - return; + | [](auto chats) { + return (!chats->empty() && chats->front().type() == mtpc_channel) + ? base::make_optional(chats) + : base::none; + } + | [](auto chats) { + return App::channel(chats->front().c_channel().vid.v); + } + | [this](gsl::not_null channel) { + if (!_photoImage.isNull()) { + Messenger::Instance().uploadProfilePhoto( + _photoImage, + channel->id); + } + _createdChannel = channel; + _creationRequestId = request( + MTPchannels_ExportInvite(_createdChannel->inputChannel) + ).done([this](const MTPExportedChatInvite &result) { + _creationRequestId = 0; + if (result.type() == mtpc_chatInviteExported) { + auto link = qs(result.c_chatInviteExported().vlink); + _createdChannel->setInviteLink(link); + } + Ui::show(Box(_createdChannel)); + }).send(); + }; + if (!success) { + LOG(("API Error: channel not found in updates (GroupInfoBox::creationDone)")); + closeBox(); } - } else { - LOG(("API Error: channel not found in updates (GroupInfoBox::creationDone)")); - } - - closeBox(); -} - -bool GroupInfoBox::creationFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - _creationRequestId = 0; - if (error.type() == "NO_CHAT_TITLE") { - _title->setFocus(); - _title->showError(); - return true; - } else if (error.type() == qstr("USER_RESTRICTED")) { - Ui::show(Box(lang(lng_cant_do_this))); - return true; - } - return false; -} - -void GroupInfoBox::exportDone(const MTPExportedChatInvite &result) { - _creationRequestId = 0; - if (result.type() == mtpc_chatInviteExported) { - _createdChannel->setInviteLink(qs(result.c_chatInviteExported().vlink)); - } - Ui::show(Box(_createdChannel)); + }).fail([this](const RPCError &error) { + _creationRequestId = 0; + if (error.type() == "NO_CHAT_TITLE") { + _title->setFocus(); + _title->showError(); + } else if (error.type() == qstr("USER_RESTRICTED")) { + Ui::show(Box(lang(lng_cant_do_this))); + } else if (error.type() == qstr("CHANNELS_TOO_MUCH")) { + Ui::show(Box(lang(lng_cant_do_this))); // TODO + } + }).send(); } void GroupInfoBox::onDescriptionResized() { @@ -503,17 +779,13 @@ void SetupChannelBox::prepare() { })); subscribe(boxClosing, [this] { if (!_existing) { - showAddContactsToChannelBox(); + ShowAddContactsToChannelBox(_channel); } }); updateMaxHeight(); } -void SetupChannelBox::showAddContactsToChannelBox() const { - Ui::show(Box(_channel)); -} - void SetupChannelBox::setInnerFocus() { if (_link->isHidden()) { setFocus(); diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index f490431715..ab4f6b7625 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mtproto/sender.h" class ConfirmBox; +class PeerListBox; namespace Ui { class FlatLabel; @@ -40,6 +41,18 @@ class LinkButton; class NewAvatarButton; } // namespace Ui +enum class PeerFloodType { + Send, + InviteGroup, + InviteChannel, +}; +QString PeerFloodErrorText(PeerFloodType type); + +void ShowAddContactsToChatBox(gsl::not_null chat); +void ShowAddContactsToChannelBox( + gsl::not_null channel, + base::flat_set> &&alreadyIn); + class AddContactBox : public BoxContent, public RPCSender { Q_OBJECT @@ -83,7 +96,7 @@ private: }; -class GroupInfoBox : public BoxContent, public RPCSender { +class GroupInfoBox : public BoxContent, private MTP::Sender { Q_OBJECT public: @@ -107,10 +120,8 @@ private slots: private: void setupPhotoButton(); - - void creationDone(const MTPUpdates &updates); - bool creationFail(const RPCError &e); - void exportDone(const MTPExportedChatInvite &result); + void createChannel(const QString &title, const QString &description); + void createGroup(gsl::not_null selectUsersBox, const QString &title, const std::vector> &users); void updateMaxHeight(); void updateSelected(const QPoint &cursorGlobalPosition); @@ -124,7 +135,7 @@ private: QImage _photoImage; - // channel creation + // group / channel creation mtpRequestId _creationRequestId = 0; ChannelData *_createdChannel = nullptr; diff --git a/Telegram/SourceFiles/boxes/contacts_box.cpp b/Telegram/SourceFiles/boxes/contacts_box.cpp index 273f2ea6a8..ca2a2bb2d8 100644 --- a/Telegram/SourceFiles/boxes/contacts_box.cpp +++ b/Telegram/SourceFiles/boxes/contacts_box.cpp @@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_profile.h" #include "lang/lang_keys.h" #include "boxes/add_contact_box.h" +#include "boxes/peer_list_box.h" #include "mainwidget.h" #include "mainwindow.h" #include "messenger.h" @@ -74,14 +75,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org // return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; //} -QString PeerFloodErrorText(PeerFloodType type) { - auto link = textcmdLink(Messenger::Instance().createInternalLinkFull(qsl("spambot")), lang(lng_cant_more_info)); - if (type == PeerFloodType::InviteGroup) { - return lng_cant_invite_not_contact(lt_more_info, link); - } - return lng_cant_send_to_not_contact(lt_more_info, link); -} - ContactsBox::ContactsBox(QWidget*, ChatData *chat, MembersFilter filter) : _chat(chat) , _membersFilter(filter) diff --git a/Telegram/SourceFiles/boxes/contacts_box.h b/Telegram/SourceFiles/boxes/contacts_box.h index 923f081aaa..f0c771f7c3 100644 --- a/Telegram/SourceFiles/boxes/contacts_box.h +++ b/Telegram/SourceFiles/boxes/contacts_box.h @@ -61,19 +61,6 @@ template class WidgetSlideWrap; } // namespace Ui -enum class PeerFloodType { - Send, - InviteGroup, - InviteChannel, -}; -QString PeerFloodErrorText(PeerFloodType type); - -inline Ui::RoundImageCheckbox::PaintRoundImage PaintUserpicCallback(PeerData *peer) { - return [peer](Painter &p, int x, int y, int outerWidth, int size) { - peer->paintUserpicLeft(p, x, y, outerWidth, size); - }; -} - class ContactsBox : public BoxContent, public RPCSender { Q_OBJECT diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 1f9a26a018..3233d305f5 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/effects/widget_slide_wrap.h" -#include "boxes/peer_list_box.h" +#include "boxes/peer_list_controllers.h" #include "apiwrap.h" #include "auth_session.h" #include "lang/lang_keys.h" @@ -178,7 +178,7 @@ void EditPrivacyBox::editExceptionUsers(Exception exception) { auto controller = std::make_unique(base::lambda_guarded(this, [this, exception] { return _controller->exceptionBoxTitle(exception); }), exceptionUsers(exception)); - auto initBox = [this, exception, controller = controller.get()](PeerListBox *box) { + auto initBox = [this, exception, controller = controller.get()](gsl::not_null box) { box->addButton(langFactory(lng_settings_save), base::lambda_guarded(this, [this, box, exception, controller] { exceptionUsers(exception) = controller->getResult(); exceptionLink(exception)->entity()->setText(exceptionLinkText(exception)); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 408f86c3e4..ec965bb69b 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -22,22 +22,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_boxes.h" #include "styles/style_dialogs.h" -#include "ui/widgets/scroll_area.h" -#include "ui/effects/ripple_animation.h" -#include "dialogs/dialogs_indexed_list.h" -#include "observer_peer.h" #include "auth_session.h" #include "mainwidget.h" -#include "storage/file_download.h" #include "ui/widgets/multi_select.h" #include "ui/widgets/labels.h" +#include "ui/effects/round_checkbox.h" +#include "ui/effects/ripple_animation.h" #include "ui/effects/widget_slide_wrap.h" #include "lang/lang_keys.h" -#include "ui/effects/round_checkbox.h" -#include "boxes/contacts_box.h" +#include "storage/file_download.h" #include "window/themes/window_theme.h" -PeerListBox::PeerListBox(QWidget*, std::unique_ptr controller, base::lambda init) +PeerListBox::PeerListBox(QWidget*, std::unique_ptr controller, base::lambda)> init) : _controller(std::move(controller)) , _init(std::move(init)) { Expects(_controller != nullptr); @@ -221,6 +217,7 @@ void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) { _inner->changeCheckState(row, false, PeerListRow::SetStyle::Animated); update(); } + _controller->itemDeselectedHook(peer); } }); _select->resizeToWidth(st::boxWideWidth); @@ -319,8 +316,12 @@ bool PeerListBox::peerListIsRowSelected(gsl::not_null peer) { return _select->entity()->hasItem(peer->id); } -std::vector> PeerListBox::peerListCollectSelectedRows() -{ +int PeerListBox::peerListSelectedRowsCount() { + Expects(_select != nullptr); + return _select->entity()->getItemsCount(); +} + +std::vector> PeerListBox::peerListCollectSelectedRows() { Expects(_select != nullptr); auto result = std::vector>(); auto items = _select->entity()->getItems(); @@ -340,7 +341,6 @@ PeerListRow::PeerListRow(gsl::not_null peer, PeerListRowId id) : _id(id) , _peer(peer) , _initialized(false) -, _disabled(false) , _isSearchResult(false) { } @@ -416,12 +416,10 @@ void PeerListRow::paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidt } void PeerListRow::paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth) { - if (_checkbox) { - if (disabled() && checked()) { - paintDisabledCheckUserpic(p, x, y, outerWidth); - } else { - _checkbox->paint(p, ms, x, y, outerWidth); - } + if (_disabledState == State::DisabledChecked) { + paintDisabledCheckUserpic(p, x, y, outerWidth); + } else if (_checkbox) { + _checkbox->paint(p, ms, x, y, outerWidth); } else { peer()->paintUserpicLeft(p, x, y, outerWidth, st::contactsPhotoSize); } @@ -898,7 +896,8 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) { namew -= icon->width(); icon->paint(p, namex + qMin(name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width()); } - p.setPen(anim::pen(st::contactsNameFg, st::contactsNameCheckedFg, row->checkedRatio())); + auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio(); + p.setPen(anim::pen(st::contactsNameFg, st::contactsNameCheckedFg, nameCheckedRatio)); name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); if (!actionSize.isEmpty()) { @@ -1263,177 +1262,3 @@ void PeerListBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names } } } - -void PeerListRowWithLink::setActionLink(const QString &action) { - _action = action; - refreshActionLink(); -} - -void PeerListRowWithLink::refreshActionLink() { - if (!isInitialized()) return; - _actionWidth = _action.isEmpty() ? 0 : st::normalFont->width(_action); -} - -void PeerListRowWithLink::lazyInitialize() { - PeerListRow::lazyInitialize(); - refreshActionLink(); -} - -QSize PeerListRowWithLink::actionSize() const { - return QSize(_actionWidth, st::normalFont->height); -} - -QMargins PeerListRowWithLink::actionMargins() const { - return QMargins(st::contactsCheckPosition.x(), (st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom() - st::normalFont->height) / 2, st::contactsCheckPosition.x(), 0); -} - -void PeerListRowWithLink::paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) { - p.setFont(actionSelected ? st::linkOverFont : st::linkFont); - p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color); - p.drawTextLeft(x, y, outerWidth, _action, _actionWidth); -} - -PeerListGlobalSearchController::PeerListGlobalSearchController() { - _timer.setCallback([this] { searchOnServer(); }); -} - -void PeerListGlobalSearchController::searchQuery(const QString &query) { - if (_query != query) { - _query = query; - _requestId = 0; - if (_query.size() >= MinUsernameLength && !searchInCache()) { - _timer.callOnce(AutoSearchTimeout); - } else { - _timer.cancel(); - } - } -} - -bool PeerListGlobalSearchController::searchInCache() { - auto it = _cache.find(_query); - if (it != _cache.cend()) { - _requestId = 0; - searchDone(it->second, _requestId); - return true; - } - return false; -} - -void PeerListGlobalSearchController::searchOnServer() { - _requestId = request(MTPcontacts_Search(MTP_string(_query), MTP_int(SearchPeopleLimit))).done([this](const MTPcontacts_Found &result, mtpRequestId requestId) { - searchDone(result, requestId); - }).fail([this](const RPCError &error, mtpRequestId requestId) { - if (_requestId == requestId) { - _requestId = 0; - delegate()->peerListSearchRefreshRows(); - } - }).send(); - _queries.emplace(_requestId, _query); -} - -void PeerListGlobalSearchController::searchDone(const MTPcontacts_Found &result, mtpRequestId requestId) { - Expects(result.type() == mtpc_contacts_found); - - auto &contacts = result.c_contacts_found(); - auto query = _query; - if (requestId) { - App::feedUsers(contacts.vusers); - App::feedChats(contacts.vchats); - auto it = _queries.find(requestId); - if (it != _queries.cend()) { - query = it->second; - _cache[query] = result; - _queries.erase(it); - } - } - if (_requestId == requestId) { - _requestId = 0; - for_const (auto &mtpPeer, contacts.vresults.v) { - if (auto peer = App::peerLoaded(peerFromMTP(mtpPeer))) { - delegate()->peerListSearchAddRow(peer); - } - } - delegate()->peerListSearchRefreshRows(); - } -} - -bool PeerListGlobalSearchController::isLoading() { - return _timer.isActive() || _requestId; -} - -ChatsListBoxController::ChatsListBoxController(std::unique_ptr searchController) : PeerListController(std::move(searchController)) { -} - -void ChatsListBoxController::prepare() { - setSearchNoResultsText(lang(lng_blocked_list_not_found)); - delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); - - prepareViewHook(); - - rebuildRows(); - - auto &sessionData = Auth().data(); - subscribe(sessionData.contactsLoaded(), [this](bool loaded) { - rebuildRows(); - }); - subscribe(sessionData.moreChatsLoaded(), [this] { - rebuildRows(); - }); - subscribe(sessionData.allChatsLoaded(), [this](bool loaded) { - checkForEmptyRows(); - }); -} - -void ChatsListBoxController::rebuildRows() { - auto ms = getms(); - auto wasEmpty = !delegate()->peerListFullRowsCount(); - auto appendList = [this](auto chats) { - auto count = 0; - for_const (auto row, chats->all()) { - auto history = row->history(); - if (history->peer->isUser()) { - if (appendRow(history)) { - ++count; - } - } - } - return count; - }; - auto added = appendList(App::main()->dialogsList()); - added += appendList(App::main()->contactsNoDialogsList()); - if (!wasEmpty && added > 0) { - // Place dialogs list before contactsNoDialogs list. - delegate()->peerListPartitionRows([](PeerListRow &a) { - auto history = static_cast(a).history(); - return history->inChatList(Dialogs::Mode::All); - }); - } - checkForEmptyRows(); - delegate()->peerListRefreshRows(); -} - -void ChatsListBoxController::checkForEmptyRows() { - if (delegate()->peerListFullRowsCount()) { - setDescriptionText(QString()); - } else { - auto &sessionData = Auth().data(); - auto loaded = sessionData.contactsLoaded().value() && sessionData.allChatsLoaded().value(); - setDescriptionText(lang(loaded ? lng_contacts_not_found : lng_contacts_loading)); - } -} - -std::unique_ptr ChatsListBoxController::createSearchRow(gsl::not_null peer) { - return createRow(App::history(peer)); -} - -bool ChatsListBoxController::appendRow(History *history) { - if (auto row = delegate()->peerListFindRow(history->peer->id)) { - updateRowHook(static_cast(row)); - return false; - } - if (auto row = createRow(history)) { - delegate()->peerListAppendRow(std::move(row)); - return true; - } - return false; -} diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index d643693560..ffe88bbfc5 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -33,14 +33,25 @@ class WidgetSlideWrap; class FlatLabel; } // namespace Ui +inline auto PaintUserpicCallback(PeerData *peer) { + return [peer](Painter &p, int x, int y, int outerWidth, int size) { + peer->paintUserpicLeft(p, x, y, outerWidth, size); + }; +} + using PeerListRowId = uint64; class PeerListRow { public: PeerListRow(gsl::not_null peer); PeerListRow(gsl::not_null peer, PeerListRowId id); - void setDisabled(bool disabled) { - _disabled = disabled; + enum class State { + Active, + Disabled, + DisabledChecked, + }; + void setDisabledState(State state) { + _disabledState = state; } // Checked state is controlled by the box with multiselect, @@ -97,7 +108,7 @@ public: return _absoluteIndex; } bool disabled() const { - return _disabled; + return (_disabledState != State::Active); } bool isSearchResult() const { return _isSearchResult; @@ -156,8 +167,8 @@ private: StatusType _statusType = StatusType::Online; OrderedSet _nameFirstChars; int _absoluteIndex = -1; + State _disabledState = State::Active; bool _initialized : 1; - bool _disabled : 1; bool _isSearchResult : 1; }; @@ -170,6 +181,7 @@ enum class PeerListSearchMode { class PeerListDelegate { public: virtual void peerListSetTitle(base::lambda title) = 0; + virtual void peerListSetAdditionalTitle(base::lambda title) = 0; virtual void peerListSetDescription(object_ptr description) = 0; virtual void peerListSetSearchLoading(object_ptr loading) = 0; virtual void peerListSetSearchNoResults(object_ptr noResults) = 0; @@ -200,6 +212,7 @@ public: peerListFinishSelectedRowsBunch(); } + virtual int peerListSelectedRowsCount() = 0; virtual std::vector> peerListCollectSelectedRows() = 0; virtual ~PeerListDelegate() = default; @@ -254,6 +267,8 @@ public: } virtual void loadMoreRows() { } + virtual void itemDeselectedHook(gsl::not_null peer) { + } bool isSearchLoading() const { return _searchController ? _searchController->isLoading() : false; } @@ -305,11 +320,14 @@ private: class PeerListBox : public BoxContent, public PeerListDelegate { public: - PeerListBox(QWidget*, std::unique_ptr controller, base::lambda init); + PeerListBox(QWidget*, std::unique_ptr controller, base::lambda)> init); void peerListSetTitle(base::lambda title) override { setTitle(std::move(title)); } + void peerListSetAdditionalTitle(base::lambda title) override { + setAdditionalTitle(std::move(title)); + } void peerListSetDescription(object_ptr description) override; void peerListSetSearchLoading(object_ptr loading) override; void peerListSetSearchNoResults(object_ptr noResults) override; @@ -325,6 +343,7 @@ public: void peerListSetRowChecked(gsl::not_null row, bool checked) override; gsl::not_null peerListRowAt(int index) override; bool peerListIsRowSelected(gsl::not_null peer) override; + int peerListSelectedRowsCount() override; std::vector> peerListCollectSelectedRows() override; void peerListRefreshRows() override; void peerListScrollToTop() override; @@ -530,77 +549,3 @@ private: std::vector> _searchRows; }; - -class PeerListRowWithLink : public PeerListRow { -public: - using PeerListRow::PeerListRow; - - void setActionLink(const QString &action); - - void lazyInitialize() override; - -private: - void refreshActionLink(); - QSize actionSize() const override; - QMargins actionMargins() const override; - void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) override; - - QString _action; - int _actionWidth = 0; - -}; - -class PeerListGlobalSearchController : public PeerListSearchController, private MTP::Sender { -public: - PeerListGlobalSearchController(); - - void searchQuery(const QString &query) override; - bool isLoading() override; - bool loadMoreRows() override { - return false; - } - -private: - bool searchInCache(); - void searchOnServer(); - void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId); - - base::Timer _timer; - QString _query; - mtpRequestId _requestId = 0; - std::map _cache; - std::map _queries; - -}; - -class ChatsListBoxController : public PeerListController, protected base::Subscriber { -public: - ChatsListBoxController(std::unique_ptr searchController = std::make_unique()); - - void prepare() override final; - std::unique_ptr createSearchRow(gsl::not_null peer) override final; - -protected: - class Row : public PeerListRow { - public: - Row(gsl::not_null history) : PeerListRow(history->peer), _history(history) { - } - gsl::not_null history() const { - return _history; - } - - private: - gsl::not_null _history; - - }; - virtual std::unique_ptr createRow(gsl::not_null history) = 0; - virtual void prepareViewHook() = 0; - virtual void updateRowHook(Row *row) { - } - -private: - void rebuildRows(); - void checkForEmptyRows(); - bool appendRow(History *history); - -}; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp new file mode 100644 index 0000000000..268d86797c --- /dev/null +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -0,0 +1,270 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "boxes/peer_list_controllers.h" + +#include "styles/style_boxes.h" +#include "auth_session.h" +#include "mainwidget.h" +#include "lang/lang_keys.h" +#include "dialogs/dialogs_indexed_list.h" + +void PeerListRowWithLink::setActionLink(const QString &action) { + _action = action; + refreshActionLink(); +} + +void PeerListRowWithLink::refreshActionLink() { + if (!isInitialized()) return; + _actionWidth = _action.isEmpty() ? 0 : st::normalFont->width(_action); +} + +void PeerListRowWithLink::lazyInitialize() { + PeerListRow::lazyInitialize(); + refreshActionLink(); +} + +QSize PeerListRowWithLink::actionSize() const { + return QSize(_actionWidth, st::normalFont->height); +} + +QMargins PeerListRowWithLink::actionMargins() const { + return QMargins(st::contactsCheckPosition.x(), (st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom() - st::normalFont->height) / 2, st::contactsCheckPosition.x(), 0); +} + +void PeerListRowWithLink::paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) { + p.setFont(actionSelected ? st::linkOverFont : st::linkFont); + p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color); + p.drawTextLeft(x, y, outerWidth, _action, _actionWidth); +} + +PeerListGlobalSearchController::PeerListGlobalSearchController() { + _timer.setCallback([this] { searchOnServer(); }); +} + +void PeerListGlobalSearchController::searchQuery(const QString &query) { + if (_query != query) { + _query = query; + _requestId = 0; + if (_query.size() >= MinUsernameLength && !searchInCache()) { + _timer.callOnce(AutoSearchTimeout); + } else { + _timer.cancel(); + } + } +} + +bool PeerListGlobalSearchController::searchInCache() { + auto it = _cache.find(_query); + if (it != _cache.cend()) { + _requestId = 0; + searchDone(it->second, _requestId); + return true; + } + return false; +} + +void PeerListGlobalSearchController::searchOnServer() { + _requestId = request(MTPcontacts_Search(MTP_string(_query), MTP_int(SearchPeopleLimit))).done([this](const MTPcontacts_Found &result, mtpRequestId requestId) { + searchDone(result, requestId); + }).fail([this](const RPCError &error, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + delegate()->peerListSearchRefreshRows(); + } + }).send(); + _queries.emplace(_requestId, _query); +} + +void PeerListGlobalSearchController::searchDone(const MTPcontacts_Found &result, mtpRequestId requestId) { + Expects(result.type() == mtpc_contacts_found); + + auto &contacts = result.c_contacts_found(); + auto query = _query; + if (requestId) { + App::feedUsers(contacts.vusers); + App::feedChats(contacts.vchats); + auto it = _queries.find(requestId); + if (it != _queries.cend()) { + query = it->second; + _cache[query] = result; + _queries.erase(it); + } + } + if (_requestId == requestId) { + _requestId = 0; + for_const (auto &mtpPeer, contacts.vresults.v) { + if (auto peer = App::peerLoaded(peerFromMTP(mtpPeer))) { + delegate()->peerListSearchAddRow(peer); + } + } + delegate()->peerListSearchRefreshRows(); + } +} + +bool PeerListGlobalSearchController::isLoading() { + return _timer.isActive() || _requestId; +} + +ChatsListBoxController::ChatsListBoxController(std::unique_ptr searchController) : PeerListController(std::move(searchController)) { +} + +void ChatsListBoxController::prepare() { + setSearchNoResultsText(lang(lng_blocked_list_not_found)); + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + + prepareViewHook(); + + rebuildRows(); + + auto &sessionData = Auth().data(); + subscribe(sessionData.contactsLoaded(), [this](bool loaded) { + rebuildRows(); + }); + subscribe(sessionData.moreChatsLoaded(), [this] { + rebuildRows(); + }); + subscribe(sessionData.allChatsLoaded(), [this](bool loaded) { + checkForEmptyRows(); + }); +} + +void ChatsListBoxController::rebuildRows() { + auto wasEmpty = !delegate()->peerListFullRowsCount(); + auto appendList = [this](auto chats) { + auto count = 0; + for_const (auto row, chats->all()) { + auto history = row->history(); + if (history->peer->isUser()) { + if (appendRow(history)) { + ++count; + } + } + } + return count; + }; + auto added = appendList(App::main()->dialogsList()); + added += appendList(App::main()->contactsNoDialogsList()); + if (!wasEmpty && added > 0) { + // Place dialogs list before contactsNoDialogs list. + delegate()->peerListPartitionRows([](PeerListRow &a) { + auto history = static_cast(a).history(); + return history->inChatList(Dialogs::Mode::All); + }); + } + checkForEmptyRows(); + delegate()->peerListRefreshRows(); +} + +void ChatsListBoxController::checkForEmptyRows() { + if (delegate()->peerListFullRowsCount()) { + setDescriptionText(QString()); + } else { + auto &sessionData = Auth().data(); + auto loaded = sessionData.contactsLoaded().value() && sessionData.allChatsLoaded().value(); + setDescriptionText(lang(loaded ? lng_contacts_not_found : lng_contacts_loading)); + } +} + +std::unique_ptr ChatsListBoxController::createSearchRow(gsl::not_null peer) { + return createRow(App::history(peer)); +} + +bool ChatsListBoxController::appendRow(gsl::not_null history) { + if (auto row = delegate()->peerListFindRow(history->peer->id)) { + updateRowHook(static_cast(row)); + return false; + } + if (auto row = createRow(history)) { + delegate()->peerListAppendRow(std::move(row)); + return true; + } + return false; +} + +ContactsBoxController::ContactsBoxController(std::unique_ptr searchController) : PeerListController(std::move(searchController)) { +} + +void ContactsBoxController::prepare() { + setSearchNoResultsText(lang(lng_blocked_list_not_found)); + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + delegate()->peerListSetTitle(langFactory(lng_contacts_header)); + + prepareViewHook(); + + rebuildRows(); + + auto &sessionData = Auth().data(); + subscribe(sessionData.contactsLoaded(), [this](bool loaded) { + rebuildRows(); + }); +} + +void ContactsBoxController::rebuildRows() { + auto appendList = [this](auto chats) { + auto count = 0; + for_const (auto row, chats->all()) { + auto history = row->history(); + if (auto user = history->peer->asUser()) { + if (appendRow(user)) { + ++count; + } + } + } + return count; + }; + appendList(App::main()->contactsList()); + checkForEmptyRows(); + delegate()->peerListRefreshRows(); +} + +void ContactsBoxController::checkForEmptyRows() { + if (delegate()->peerListFullRowsCount()) { + setDescriptionText(QString()); + } else { + auto &sessionData = Auth().data(); + auto loaded = sessionData.contactsLoaded().value(); + setDescriptionText(lang(loaded ? lng_contacts_not_found : lng_contacts_loading)); + } +} + +std::unique_ptr ContactsBoxController::createSearchRow(gsl::not_null peer) { + return createRow(peer->asUser()); +} + +void ContactsBoxController::rowClicked(gsl::not_null row) { + Ui::showPeerHistory(row->peer(), ShowAtUnreadMsgId); +} + +bool ContactsBoxController::appendRow(gsl::not_null user) { + if (auto row = delegate()->peerListFindRow(user->id)) { + updateRowHook(row); + return false; + } + if (auto row = createRow(user)) { + delegate()->peerListAppendRow(std::move(row)); + return true; + } + return false; +} + +std::unique_ptr ContactsBoxController::createRow(gsl::not_null user) { + return std::make_unique(user); +} diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h new file mode 100644 index 0000000000..bcafa25d11 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -0,0 +1,119 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "peer_list_box.h" + +class PeerListRowWithLink : public PeerListRow { +public: + using PeerListRow::PeerListRow; + + void setActionLink(const QString &action); + + void lazyInitialize() override; + +private: + void refreshActionLink(); + QSize actionSize() const override; + QMargins actionMargins() const override; + void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) override; + + QString _action; + int _actionWidth = 0; + +}; + +class PeerListGlobalSearchController : public PeerListSearchController, private MTP::Sender { +public: + PeerListGlobalSearchController(); + + void searchQuery(const QString &query) override; + bool isLoading() override; + bool loadMoreRows() override { + return false; + } + +private: + bool searchInCache(); + void searchOnServer(); + void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId); + + base::Timer _timer; + QString _query; + mtpRequestId _requestId = 0; + std::map _cache; + std::map _queries; + +}; + +class ChatsListBoxController : public PeerListController, protected base::Subscriber { +public: + ChatsListBoxController(std::unique_ptr searchController = std::make_unique()); + + void prepare() override final; + std::unique_ptr createSearchRow(gsl::not_null peer) override final; + +protected: + class Row : public PeerListRow { + public: + Row(gsl::not_null history) : PeerListRow(history->peer), _history(history) { + } + gsl::not_null history() const { + return _history; + } + + private: + gsl::not_null _history; + + }; + virtual std::unique_ptr createRow(gsl::not_null history) = 0; + virtual void prepareViewHook() = 0; + virtual void updateRowHook(gsl::not_null row) { + } + +private: + void rebuildRows(); + void checkForEmptyRows(); + bool appendRow(gsl::not_null history); + +}; + +class ContactsBoxController : public PeerListController, protected base::Subscriber { +public: + ContactsBoxController(std::unique_ptr searchController = std::make_unique()); + + void prepare() override final; + std::unique_ptr createSearchRow(gsl::not_null peer) override final; + void rowClicked(gsl::not_null row) override; + +protected: + virtual std::unique_ptr createRow(gsl::not_null user); + virtual void prepareViewHook() { + } + virtual void updateRowHook(gsl::not_null row) { + } + +private: + void rebuildRows(); + void checkForEmptyRows(); + bool appendRow(gsl::not_null user); + +}; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 75b89c9408..0936070e55 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -38,7 +38,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "window/themes/window_theme.h" -#include "boxes/contacts_box.h" +#include "boxes/peer_list_box.h" #include "auth_session.h" #include "messenger.h" diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h index 96e7e92833..a214a8b784 100644 --- a/Telegram/SourceFiles/core/utils.h +++ b/Telegram/SourceFiles/core/utils.h @@ -617,7 +617,7 @@ enum ShowLayerOption { AnimatedShowLayer = 0x00, ForceFastShowLayer = 0x04, }; -Q_DECLARE_FLAGS(ShowLayerOptions, ShowLayerOption); +using ShowLayerOptions = QFlags; Q_DECLARE_OPERATORS_FOR_FLAGS(ShowLayerOptions); static int32 FullArcLength = 360 * 16; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index fb104e8ad5..7e679edd2f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -181,7 +181,7 @@ private: MessageSearch = 0x08, All = 0x0F, }; - Q_DECLARE_FLAGS(UpdateRowSections, UpdateRowSection); + using UpdateRowSections = QFlags; Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(UpdateRowSections); void updateDialogRow(PeerData *peer, MsgId msgId, QRect updateRect, UpdateRowSections sections = UpdateRowSection::All); diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index d44294aaa8..e38a38ca80 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -545,7 +545,7 @@ private: f_has_pending_resized_items = (1 << 0), f_pending_resize = (1 << 1), }; - Q_DECLARE_FLAGS(Flags, Flag); + using Flags = QFlags; Q_DECL_CONSTEXPR friend inline QFlags operator|(Flags::enum_type f1, Flags::enum_type f2) noexcept { return QFlags(f1) | f2; } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index bd887ccc4a..9e06ff8c33 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -174,7 +174,7 @@ struct HistoryMessageReply : public RuntimeComponent { PaintInBubble = 0x01, PaintSelected = 0x02, }; - Q_DECLARE_FLAGS(PaintFlags, PaintFlag); + using PaintFlags = QFlags; void paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const; MsgId replyToId() const { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 2097051ca1..c9d593b82d 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -689,7 +689,7 @@ private: SaveDraft = 0x01, SendTyping = 0x02, }; - Q_DECLARE_FLAGS(TextUpdateEvents, TextUpdateEvent); + using TextUpdateEvents = QFlags; Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(TextUpdateEvents); void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft); diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h index cb159fd875..8922e1827e 100644 --- a/Telegram/SourceFiles/observer_peer.h +++ b/Telegram/SourceFiles/observer_peer.h @@ -72,7 +72,7 @@ struct PeerUpdate { ChannelRightsChanged = 0x00020000U, ChannelStickersChanged = 0x00040000U, }; - Q_DECLARE_FLAGS(Flags, Flag); + using Flags = QFlags; Flags flags = 0; // NameChanged data diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index ea23b5557f..1254911187 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "window/notifications_manager_default.h" #include "platform/platform_notifications_manager.h" #include "boxes/contacts_box.h" +#include "boxes/peer_list_controllers.h" #include "boxes/about_box.h" #include "lang/lang_keys.h" #include "platform/mac/mac_utilities.h" @@ -437,7 +438,10 @@ void MainWindow::createGlobalMenu() { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); if (!App::self()) return; - Ui::show(Box()); + Ui::show(Box(std::make_unique(), [](gsl::not_null box) { + box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); + box->addLeftButton(langFactory(lng_profile_add_contact), [] { App::wnd()->onShowAddContact(); }); + })); }); psAddContact = window->addAction(lang(lng_mac_menu_add_contact), App::wnd(), SLOT(onShowAddContact())); window->addSeparator(); diff --git a/Telegram/SourceFiles/platform/win/main_window_win.h b/Telegram/SourceFiles/platform/win/main_window_win.h index 97b8c54be2..b080e20a6d 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.h +++ b/Telegram/SourceFiles/platform/win/main_window_win.h @@ -60,7 +60,7 @@ public: Hidden = 0x08, Activate = 0x10, }; - Q_DECLARE_FLAGS(ShadowsChanges, ShadowsChange); + using ShadowsChanges = QFlags; bool shadowsWorking() const { return _shadowsWorking; diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp index 78a0ab88e9..7ac655f603 100644 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp +++ b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp @@ -20,8 +20,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "profile/profile_channel_controllers.h" +#include "boxes/peer_list_controllers.h" #include "boxes/edit_participant_box.h" #include "boxes/confirm_box.h" +#include "boxes/add_contact_box.h" #include "boxes/contacts_box.h" #include "auth_session.h" #include "apiwrap.h" @@ -55,7 +57,7 @@ std::unique_ptr ParticipantsBoxController::CreateSearc void ParticipantsBoxController::Start(gsl::not_null channel, Role role) { auto controller = std::make_unique(channel, role); - auto initBox = [role, channel, controller = controller.get()](PeerListBox *box) { + auto initBox = [role, channel, controller = controller.get()](gsl::not_null box) { box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); auto canAddNewItem = [role, channel] { switch (role) { @@ -87,12 +89,12 @@ void ParticipantsBoxController::addNewItem() { if (_channel->membersCount() >= Global::ChatSizeMax()) { Ui::show(Box(_channel), KeepOtherLayers); } else { - auto already = MembersAlreadyIn(); + auto already = std::vector>(); + already.reserve(delegate()->peerListFullRowsCount()); for (auto i = 0, count = delegate()->peerListFullRowsCount(); i != count; ++i) { - auto user = delegate()->peerListRowAt(i)->peer()->asUser(); - already.insert(user); + already.push_back(delegate()->peerListRowAt(i)->peer()->asUser()); } - Ui::show(Box(_channel, MembersFilter::Recent, already)); + ShowAddContactsToChannelBox(_channel, { already.begin(), already.end() }); } return; } @@ -105,7 +107,7 @@ void ParticipantsBoxController::addNewItem() { if (weak) { weak->editRestrictedDone(user, rights); } - }), [](PeerListBox *box) { + }), [](gsl::not_null box) { box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }), KeepOtherLayers); } diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index aba0130a42..99c641dad2 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -31,6 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/confirm_box.h" #include "boxes/contacts_box.h" #include "boxes/photo_crop_box.h" +#include "boxes/add_contact_box.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "auth_session.h" @@ -538,14 +539,11 @@ void CoverWidget::onAddMember() { if (_peerChat->count >= Global::ChatSizeMax() && _peerChat->amCreator()) { Ui::show(Box(_peerChat)); } else { - Ui::show(Box(_peerChat, MembersFilter::Recent)); + ShowAddContactsToChatBox(_peerChat); } } else if (_peerChannel && _peerChannel->mgInfo) { - MembersAlreadyIn already; - for_const (auto user, _peerChannel->mgInfo->lastParticipants) { - already.insert(user); - } - Ui::show(Box(_peerChannel, MembersFilter::Recent, already)); + auto &participants = _peerChannel->mgInfo->lastParticipants; + ShowAddContactsToChannelBox(_peerChannel, { participants.cbegin(), participants.cend() }); } } diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index a020f3d1dd..9ae43554f8 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "auth_session.h" #include "storage/localstorage.h" +#include "boxes/peer_list_controllers.h" #include "boxes/confirm_box.h" namespace Settings { @@ -44,7 +45,7 @@ public: protected: void prepareViewHook() override; std::unique_ptr createRow(gsl::not_null history) override; - void updateRowHook(Row *row) override { + void updateRowHook(gsl::not_null row) override { updateIsBlocked(row, row->peer()->asUser()); delegate()->peerListUpdateRow(row); } @@ -70,7 +71,7 @@ void BlockUserBoxController::prepareViewHook() { void BlockUserBoxController::updateIsBlocked(gsl::not_null row, UserData *user) const { auto blocked = user->isBlocked(); - row->setDisabled(blocked); + row->setDisabledState(blocked ? PeerListRow::State::DisabledChecked : PeerListRow::State::Active); if (blocked) { row->setCustomStatus(lang(lng_blocked_list_already_blocked)); } else { @@ -186,7 +187,7 @@ void BlockedBoxController::handleBlockedEvent(UserData *user) { void BlockedBoxController::BlockNewUser() { auto controller = std::make_unique(); - auto initBox = [controller = controller.get()](PeerListBox *box) { + auto initBox = [controller = controller.get()](gsl::not_null box) { controller->setBlockUserCallback([box](gsl::not_null user) { Auth().api().blockUser(user); box->closeBox(); diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp index 9ab61c1529..3aa3141cee 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp @@ -208,7 +208,7 @@ void PrivacyWidget::autoLockUpdated() { } void PrivacyWidget::onBlockedUsers() { - Ui::show(Box(std::make_unique(), [](PeerListBox *box) { + Ui::show(Box(std::make_unique(), [](gsl::not_null box) { box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); box->addLeftButton(langFactory(lng_blocked_list_add), [box] { BlockedBoxController::BlockNewUser(); }); })); diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 584ad0523e..319a99b8fa 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -78,7 +78,7 @@ enum class FileOption { User = 0x01, Safe = 0x02, }; -Q_DECLARE_FLAGS(FileOptions, FileOption); +using FileOptions = QFlags; Q_DECLARE_OPERATORS_FOR_FLAGS(FileOptions); bool keyAlreadyUsed(QString &name, FileOptions options = FileOption::User | FileOption::Safe) { diff --git a/Telegram/SourceFiles/ui/abstract_button.h b/Telegram/SourceFiles/ui/abstract_button.h index a564ab56a9..657280b431 100644 --- a/Telegram/SourceFiles/ui/abstract_button.h +++ b/Telegram/SourceFiles/ui/abstract_button.h @@ -80,7 +80,7 @@ protected: Down = 0x02, Disabled = 0x04, }; - Q_DECLARE_FLAGS(State, StateFlag); + using State = QFlags; Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(State); State state() const { diff --git a/Telegram/SourceFiles/ui/images.h b/Telegram/SourceFiles/ui/images.h index 3a6d67875e..f9c5809f06 100644 --- a/Telegram/SourceFiles/ui/images.h +++ b/Telegram/SourceFiles/ui/images.h @@ -47,7 +47,7 @@ enum class ImageRoundCorner { BottomRight = 0x08, All = 0x0f, }; -Q_DECLARE_FLAGS(ImageRoundCorners, ImageRoundCorner); +using ImageRoundCorners = QFlags; Q_DECLARE_OPERATORS_FOR_FLAGS(ImageRoundCorners); inline uint32 packInt(int32 a) { @@ -199,7 +199,7 @@ enum class Option { Colored = 0x200, TransparentBackground = 0x400, }; -Q_DECLARE_FLAGS(Options, Option); +using Options = QFlags