From 7fdac9cd94f44da03cf5a94af2e36b326d6dfdb1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 14 Jun 2017 00:08:47 +0300 Subject: [PATCH] Add restricted users box like kicked users box. Also allow server-side search inside restricted users list. Also allow server-side search inside kicked users list. Also allow PeerListController to work not only in PeerListBox. --- Telegram/Resources/langs/lang.strings | 5 +- .../SourceFiles/boxes/edit_privacy_box.cpp | 107 +-- Telegram/SourceFiles/boxes/edit_privacy_box.h | 6 +- Telegram/SourceFiles/boxes/members_box.cpp | 2 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 569 +++++++++------- Telegram/SourceFiles/boxes/peer_list_box.h | 625 ++++++++++-------- .../calls/calls_box_controller.cpp | 56 +- .../SourceFiles/calls/calls_box_controller.h | 8 +- .../profile/profile_block_group_members.cpp | 8 +- .../profile/profile_block_settings.cpp | 274 ++++++-- .../profile/profile_block_settings.h | 2 + .../settings/settings_privacy_controllers.cpp | 82 +-- .../settings/settings_privacy_controllers.h | 11 +- .../settings/settings_privacy_widget.cpp | 5 +- .../SourceFiles/window/window_main_menu.cpp | 4 +- 15 files changed, 1062 insertions(+), 702 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 13baa9b6f1..61fb3ea541 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -544,6 +544,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_profile_edit_public_link" = "Edit public link"; "lng_profile_manage_admins" = "Manage administrators"; "lng_profile_manage_blocklist" = "Manage blocked users"; +"lng_profile_manage_restrictedlist" = "Manage restricted users"; "lng_profile_common_groups#one" = "{count} group in common"; "lng_profile_common_groups#other" = "{count} groups in common"; "lng_profile_common_groups_section" = "Groups in common"; @@ -579,7 +580,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_profile_delete_and_exit" = "Leave"; "lng_profile_kick" = "Remove"; "lng_profile_admin" = "admin"; -"lng_profile_edit_admin" = "Edit"; +"lng_profile_edit_permissions" = "Edit"; "lng_profile_sure_kick" = "Remove {user} from the group?"; "lng_profile_sure_kick_channel" = "Remove {user} from the channel?"; "lng_profile_sure_kick_admin" = "Remove {user} from administrators?"; @@ -1283,6 +1284,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_restricted_send_gifs" = "The admins of this group restricted you from posting GIFs here."; "lng_restricted_send_inline" = "The admins of this group restricted you from posting inline content here."; +"lng_restricted_list_title" = "Restricted users"; + // Not used "lng_topbar_info" = "Info"; diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index b940bd4f9e..0de16c1016 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -33,51 +33,50 @@ namespace { class PrivacyExceptionsBoxController : public ChatsListBoxController { public: - PrivacyExceptionsBoxController(base::lambda titleFactory, const QVector &selected, base::lambda_once &&result)> saveCallback); - void rowClicked(PeerListBox::Row *row) override; + PrivacyExceptionsBoxController(base::lambda titleFactory, const std::vector> &selected); + void rowClicked(gsl::not_null row) override; + + std::vector> getResult() const; protected: void prepareViewHook() override; - std::unique_ptr createRow(History *history) override; + std::unique_ptr createRow(gsl::not_null history) override; private: base::lambda _titleFactory; - QVector _selected; - base::lambda_once &&result)> _saveCallback; + std::vector> _selected; }; -PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(base::lambda titleFactory, const QVector &selected, base::lambda_once &&result)> saveCallback) +PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(base::lambda titleFactory, const std::vector> &selected) : _titleFactory(std::move(titleFactory)) -, _selected(selected) -, _saveCallback(std::move(saveCallback)) { +, _selected(selected) { } void PrivacyExceptionsBoxController::prepareViewHook() { - view()->setTitle(_titleFactory); - view()->addButton(langFactory(lng_settings_save), [this] { - auto peers = view()->collectSelectedRows(); - auto users = QVector(); - if (!peers.empty()) { - users.reserve(peers.size()); - for_const (auto peer, peers) { - auto user = peer->asUser(); - t_assert(user != nullptr); - users.push_back(user); - } + delegate()->peerListSetTitle(_titleFactory); + delegate()->peerListAddSelectedRows(_selected); +} + +std::vector> PrivacyExceptionsBoxController::getResult() const { + auto peers = delegate()->peerListCollectSelectedRows(); + auto users = std::vector>(); + if (!peers.empty()) { + users.reserve(peers.size()); + for_const (auto peer, peers) { + auto user = peer->asUser(); + t_assert(user != nullptr); + users.push_back(user); } - _saveCallback(std::move(users)); - view()->closeBox(); - }); - view()->addButton(langFactory(lng_cancel), [this] { view()->closeBox(); }); - view()->addSelectedRows(_selected); + } + return users; } -void PrivacyExceptionsBoxController::rowClicked(PeerListBox::Row *row) { - view()->setRowChecked(row, !row->checked()); +void PrivacyExceptionsBoxController::rowClicked(gsl::not_null row) { + delegate()->peerListSetRowChecked(row, !row->checked()); } -std::unique_ptr PrivacyExceptionsBoxController::createRow(History *history) { +std::unique_ptr PrivacyExceptionsBoxController::createRow(gsl::not_null history) { if (auto user = history->peer->asUser()) { if (!user->isSelf()) { return std::make_unique(history); @@ -176,29 +175,35 @@ int EditPrivacyBox::countDefaultHeight(int newWidth) { void EditPrivacyBox::editExceptionUsers(Exception exception) { auto controller = std::make_unique(base::lambda_guarded(this, [this, exception] { return _controller->exceptionBoxTitle(exception); - }), exceptionUsers(exception), base::lambda_guarded(this, [this, exception](QVector &&users) { - exceptionUsers(exception) = std::move(users); - exceptionLink(exception)->entity()->setText(exceptionLinkText(exception)); - auto removeFrom = ([exception] { - switch (exception) { - case Exception::Always: return Exception::Never; - case Exception::Never: return Exception::Always; + }), exceptionUsers(exception)); + auto initBox = [this, exception, controller = controller.get()](PeerListBox *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)); + auto removeFrom = ([exception] { + switch (exception) { + case Exception::Always: return Exception::Never; + case Exception::Never: return Exception::Always; + } + Unexpected("Invalid exception value."); + })(); + auto &removeFromUsers = exceptionUsers(removeFrom); + auto removedSome = false; + for (auto user : exceptionUsers(exception)) { + auto removedStart = std::remove(removeFromUsers.begin(), removeFromUsers.end(), user); + if (removedStart != removeFromUsers.end()) { + removeFromUsers.erase(removedStart, removeFromUsers.end()); + removedSome = true; + } } - Unexpected("Invalid exception value."); - })(); - auto &removeFromUsers = exceptionUsers(removeFrom); - auto removedSome = false; - for (auto user : exceptionUsers(exception)) { - if (removeFromUsers.contains(user)) { - removeFromUsers.erase(std::remove(removeFromUsers.begin(), removeFromUsers.end(), user), removeFromUsers.end()); - removedSome = true; + if (removedSome) { + exceptionLink(removeFrom)->entity()->setText(exceptionLinkText(removeFrom)); } - } - if (removedSome) { - exceptionLink(removeFrom)->entity()->setText(exceptionLinkText(removeFrom)); - } - })); - Ui::show(Box(std::move(controller)), KeepOtherLayers); + box->closeBox(); + })); + box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); + }; + Ui::show(Box(std::move(controller), std::move(initBox)), KeepOtherLayers); } QString EditPrivacyBox::exceptionLinkText(Exception exception) { @@ -237,7 +242,7 @@ style::margins EditPrivacyBox::exceptionLinkMargins() const { return st::editPrivacyLinkMargin; } -QVector &EditPrivacyBox::exceptionUsers(Exception exception) { +std::vector> &EditPrivacyBox::exceptionUsers(Exception exception) { switch (exception) { case Exception::Always: return _alwaysUsers; case Exception::Never: return _neverUsers; @@ -339,7 +344,7 @@ void EditPrivacyBox::loadData() { _alwaysUsers.reserve(_alwaysUsers.size() + users.size()); for (auto &userId : users) { auto user = App::user(UserId(userId.v)); - if (!_neverUsers.contains(user) && !_alwaysUsers.contains(user)) { + if (!base::contains(_neverUsers, user) && !base::contains(_alwaysUsers, user)) { _alwaysUsers.push_back(user); } } @@ -351,7 +356,7 @@ void EditPrivacyBox::loadData() { _neverUsers.reserve(_neverUsers.size() + users.size()); for (auto &userId : users) { auto user = App::user(UserId(userId.v)); - if (!_alwaysUsers.contains(user) && !_neverUsers.contains(user)) { + if (!base::contains(_alwaysUsers, user) && !base::contains(_neverUsers, user)) { _neverUsers.push_back(user); } } diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.h b/Telegram/SourceFiles/boxes/edit_privacy_box.h index fb8aa4e7ff..0d54281158 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.h +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.h @@ -102,7 +102,7 @@ private: void editExceptionUsers(Exception exception); QString exceptionLinkText(Exception exception); - QVector &exceptionUsers(Exception exception); + std::vector> &exceptionUsers(Exception exception); object_ptr> &exceptionLink(Exception exception); std::unique_ptr _controller; @@ -120,7 +120,7 @@ private: object_ptr> _neverLink = { nullptr }; object_ptr _exceptionsDescription = { nullptr }; - QVector _alwaysUsers; - QVector _neverUsers; + std::vector> _alwaysUsers; + std::vector> _neverUsers; }; diff --git a/Telegram/SourceFiles/boxes/members_box.cpp b/Telegram/SourceFiles/boxes/members_box.cpp index 6c6babfb95..35cff4df88 100644 --- a/Telegram/SourceFiles/boxes/members_box.cpp +++ b/Telegram/SourceFiles/boxes/members_box.cpp @@ -163,7 +163,7 @@ MembersBox::Inner::Inner(QWidget *parent, gsl::not_null channel, M , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _channel(channel) , _filter(filter) -, _kickText(lang((filter == MembersFilter::Admins) ? lng_profile_edit_admin : lng_profile_kick)) +, _kickText(lang((filter == MembersFilter::Admins) ? lng_profile_edit_permissions : lng_profile_kick)) , _kickWidth(st::normalFont->width(_kickText)) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right()) , _about(_aboutWidth) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 4c772e8451..576d99c4b1 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -37,8 +37,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/contacts_box.h" #include "window/themes/window_theme.h" -PeerListBox::PeerListBox(QWidget*, std::unique_ptr controller) -: _controller(std::move(controller)) { +PeerListBox::PeerListBox(QWidget*, std::unique_ptr controller, base::lambda init) +: _controller(std::move(controller)) +, _init(std::move(init)) { + Expects(_controller != nullptr); } object_ptr> PeerListBox::createMultiSelect() { @@ -63,7 +65,7 @@ void PeerListBox::updateScrollSkips() { void PeerListBox::prepare() { _inner = setInnerWidget(object_ptr(this, _controller.get()), st::boxLayerScroll); - _controller->setView(this); + _controller->setDelegate(this); setDimensions(st::boxWideWidth, st::boxMaxListHeight); if (_select) { @@ -72,6 +74,10 @@ void PeerListBox::prepare() { } connect(_inner, SIGNAL(mustScrollTo(int, int)), this, SLOT(onScrollToY(int, int))); + + if (_init) { + _init(this); + } } void PeerListBox::keyPressEvent(QKeyEvent *e) { @@ -116,76 +122,88 @@ void PeerListBox::setInnerFocus() { } } -void PeerListBox::appendRow(std::unique_ptr row) { +void PeerListBox::peerListAppendRow(std::unique_ptr row) { _inner->appendRow(std::move(row)); } -void PeerListBox::prependRow(std::unique_ptr row) { +void PeerListBox::peerListAppendSearchRow(std::unique_ptr row) { + _inner->appendSearchRow(std::move(row)); +} + +void PeerListBox::peerListAppendFoundRow(gsl::not_null row) { + _inner->appendFoundRow(row); +} + +void PeerListBox::peerListPrependRow(std::unique_ptr row) { _inner->prependRow(std::move(row)); } -PeerListBox::Row *PeerListBox::findRow(RowId id) { +PeerListRow *PeerListBox::peerListFindRow(PeerListRowId id) { return _inner->findRow(id); } -void PeerListBox::updateRow(Row *row) { +void PeerListBox::peerListUpdateRow(gsl::not_null row) { _inner->updateRow(row); } -void PeerListBox::removeRow(Row *row) { +void PeerListBox::peerListRemoveRow(gsl::not_null row) { _inner->removeRow(row); } -void PeerListBox::setRowChecked(Row *row, bool checked) { +void PeerListBox::peerListSetRowChecked(gsl::not_null row, bool checked) { auto peer = row->peer(); if (checked) { - addSelectItem(peer, Row::SetStyle::Animated); - _inner->changeCheckState(row, checked, Row::SetStyle::Animated); - updateRow(row); + addSelectItem(peer, PeerListRow::SetStyle::Animated); + _inner->changeCheckState(row, checked, PeerListRow::SetStyle::Animated); + peerListUpdateRow(row); - // This call deletes row from _globalSearchRows. + // This call deletes row from _searchRows. _select->entity()->clearQuery(); } else { // The itemRemovedCallback will call changeCheckState() here. _select->entity()->removeItem(peer->id); - updateRow(row); + peerListUpdateRow(row); } } -int PeerListBox::fullRowsCount() const { +int PeerListBox::peerListFullRowsCount() { return _inner->fullRowsCount(); } -PeerListBox::Row *PeerListBox::rowAt(int index) const { +gsl::not_null PeerListBox::peerListRowAt(int index) { return _inner->rowAt(index); } -void PeerListBox::setAboutText(const QString &aboutText) { - if (aboutText.isEmpty()) { - setAbout(nullptr); - } else { - setAbout(object_ptr(this, aboutText, Ui::FlatLabel::InitType::Simple, st::membersAbout)); - } -} - -void PeerListBox::setAbout(object_ptr about) { - _inner->setAbout(std::move(about)); -} - -void PeerListBox::refreshRows() { +void PeerListBox::peerListRefreshRows() { _inner->refreshRows(); } -void PeerListBox::setSearchMode(SearchMode mode) { +void PeerListBox::peerListScrollToTop() { + onScrollToY(0); +} + +void PeerListBox::peerListSetDescription(object_ptr description) { + _inner->setDescription(std::move(description)); +} + +void PeerListBox::peerListSetSearchLoading(object_ptr loading) { + _inner->setSearchLoading(std::move(loading)); +} + +void PeerListBox::peerListSetSearchNoResults(object_ptr noResults) { + _inner->setSearchNoResults(std::move(noResults)); +} + +void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) { _inner->setSearchMode(mode); - if (mode != SearchMode::None && !_select) { + if (mode != PeerListSearchMode::None && !_select) { _select = createMultiSelect(); _select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { _inner->submitted(); }); _select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); }); _select->entity()->setItemRemovedCallback([this](uint64 itemId) { if (auto peer = App::peerLoaded(itemId)) { - if (auto row = findRow(peer->id)) { - _inner->changeCheckState(row, false, Row::SetStyle::Animated); + if (auto row = peerListFindRow(peer->id)) { + _inner->changeCheckState(row, false, PeerListRow::SetStyle::Animated); update(); } } @@ -194,37 +212,98 @@ void PeerListBox::setSearchMode(SearchMode mode) { _select->moveToLeft(0, 0); } if (_select) { - _select->toggleAnimated(mode != SearchMode::None); + _select->toggleAnimated(mode != PeerListSearchMode::None); } } -void PeerListBox::setSearchNoResultsText(const QString &searchNoResultsText) { - if (searchNoResultsText.isEmpty()) { - setSearchNoResults(nullptr); +void PeerListBox::peerListSortRows(base::lambda compare) { + _inner->reorderRows([compare = std::move(compare)](auto &&begin, auto &&end) { + std::sort(begin, end, [&compare](auto &&a, auto &&b) { + return compare(*a, *b); + }); + }); +} + +void PeerListBox::peerListPartitionRows(base::lambda border) { + _inner->reorderRows([border = std::move(border)](auto &&begin, auto &&end) { + std::stable_partition(begin, end, [&border](auto &¤t) { + return border(*current); + }); + }); +} + +PeerListController::PeerListController(std::unique_ptr searchController) : _searchController(std::move(searchController)) { + if (_searchController) { + _searchController->setDelegate(this); + } +} + +void PeerListController::search(const QString &query) { + Expects(_searchController != nullptr); + _searchController->searchQuery(query); +} + +void PeerListController::peerListSearchAddRow(gsl::not_null peer) { + if (auto row = delegate()->peerListFindRow(peer->id)) { + t_assert(row->id() == row->peer()->id); + delegate()->peerListAppendFoundRow(row); + } else if (auto row = createSearchRow(peer)) { + t_assert(row->id() == row->peer()->id); + delegate()->peerListAppendSearchRow(std::move(row)); + } +} + +void PeerListController::peerListSearchRefreshRows() { + delegate()->peerListRefreshRows(); +} + +void PeerListController::setDescriptionText(const QString &text) { + if (text.isEmpty()) { + setDescription(nullptr); } else { - setSearchNoResults(object_ptr(this, searchNoResultsText, Ui::FlatLabel::InitType::Simple, st::membersAbout)); + setDescription(object_ptr(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout)); } } -void PeerListBox::setSearchNoResults(object_ptr searchNoResults) { - _inner->setSearchNoResults(std::move(searchNoResults)); -} - -void PeerListBox::setSearchLoadingText(const QString &searchLoadingText) { - if (searchLoadingText.isEmpty()) { +void PeerListController::setSearchLoadingText(const QString &text) { + if (text.isEmpty()) { setSearchLoading(nullptr); } else { - setSearchLoading(object_ptr(this, searchLoadingText, Ui::FlatLabel::InitType::Simple, st::membersAbout)); + setSearchLoading(object_ptr(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout)); } } -void PeerListBox::setSearchLoading(object_ptr searchLoading) { - _inner->setSearchLoading(std::move(searchLoading)); +void PeerListController::setSearchNoResultsText(const QString &text) { + if (text.isEmpty()) { + setSearchNoResults(nullptr); + } else { + setSearchNoResults(object_ptr(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout)); + } } -QVector PeerListBox::collectSelectedRows() const { +void PeerListBox::addSelectItem(gsl::not_null peer, PeerListRow::SetStyle style) { Expects(_select != nullptr); - auto result = QVector(); + if (style == PeerListRow::SetStyle::Fast) { + _select->entity()->addItemInBunch(peer->id, peer->shortName(), st::activeButtonBg, PaintUserpicCallback(peer)); + } else { + _select->entity()->addItem(peer->id, peer->shortName(), st::activeButtonBg, PaintUserpicCallback(peer), Ui::MultiSelect::AddItemWay::Default); + } +} + +void PeerListBox::peerListFinishSelectedRowsBunch() { + Expects(_select != nullptr); + _select->entity()->finishItemsBunch(); +} + +bool PeerListBox::peerListIsRowSelected(gsl::not_null peer) { + Expects(_select != nullptr); + return _select->entity()->hasItem(peer->id); +} + +std::vector> PeerListBox::peerListCollectSelectedRows() +{ + Expects(_select != nullptr); + auto result = std::vector>(); auto items = _select->entity()->getItems(); if (!items.empty()) { result.reserve(items.size()); @@ -235,46 +314,27 @@ QVector PeerListBox::collectSelectedRows() const { return result; } -void PeerListBox::addSelectItem(PeerData *peer, Row::SetStyle style) { - Expects(_select != nullptr); - if (style == Row::SetStyle::Fast) { - _select->entity()->addItemInBunch(peer->id, peer->shortName(), st::activeButtonBg, PaintUserpicCallback(peer)); - } else { - _select->entity()->addItem(peer->id, peer->shortName(), st::activeButtonBg, PaintUserpicCallback(peer), Ui::MultiSelect::AddItemWay::Default); - } +PeerListRow::PeerListRow(gsl::not_null peer) : PeerListRow(peer, peer->id) { } -void PeerListBox::finishSelectItemsBunch() { - Expects(_select != nullptr); - _select->entity()->finishItemsBunch(); +PeerListRow::PeerListRow(gsl::not_null peer, PeerListRowId id) : _id(id), _peer(peer) { } -bool PeerListBox::isRowSelected(PeerData *peer) const { - Expects(_select != nullptr); - return _select->entity()->hasItem(peer->id); -} - -PeerListBox::Row::Row(PeerData *peer) : Row(peer, peer->id) { -} - -PeerListBox::Row::Row(PeerData *peer, RowId id) : _id(id), _peer(peer) { -} - -bool PeerListBox::Row::checked() const { +bool PeerListRow::checked() const { return _checkbox && _checkbox->checked(); } -void PeerListBox::Row::setCustomStatus(const QString &status) { +void PeerListRow::setCustomStatus(const QString &status) { _status = status; _statusType = StatusType::Custom; } -void PeerListBox::Row::clearCustomStatus() { +void PeerListRow::clearCustomStatus() { _statusType = StatusType::Online; refreshStatus(); } -void PeerListBox::Row::refreshStatus() { +void PeerListRow::refreshStatus() { if (!_initialized || _statusType == StatusType::Custom) { return; } @@ -285,29 +345,29 @@ void PeerListBox::Row::refreshStatus() { } } -void PeerListBox::Row::refreshName() { +void PeerListRow::refreshName() { if (!_initialized) { return; } _name.setText(st::contactsNameStyle, peer()->name, _textNameOptions); } -PeerListBox::Row::~Row() = default; +PeerListRow::~PeerListRow() = default; -void PeerListBox::Row::invalidatePixmapsCache() { +void PeerListRow::invalidatePixmapsCache() { if (_checkbox) { _checkbox->invalidateCache(); } } -void PeerListBox::Row::paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected) { - auto statusHasOnlineColor = (_statusType == Row::StatusType::Online); +void PeerListRow::paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected) { + auto statusHasOnlineColor = (_statusType == PeerListRow::StatusType::Online); p.setPen(statusHasOnlineColor ? st::contactsStatusFgOnline : (selected ? st::contactsStatusFgOver : st::contactsStatusFg)); p.drawTextLeft(x, y, outerWidth, _status); } template -void PeerListBox::Row::addRipple(QSize size, QPoint point, UpdateCallback updateCallback) { +void PeerListRow::addRipple(QSize size, QPoint point, UpdateCallback updateCallback) { if (!_ripple) { auto mask = Ui::RippleAnimation::rectMask(size); _ripple = std::make_unique(st::contactsRipple, std::move(mask), std::move(updateCallback)); @@ -315,13 +375,13 @@ void PeerListBox::Row::addRipple(QSize size, QPoint point, UpdateCallback update _ripple->add(point); } -void PeerListBox::Row::stopLastRipple() { +void PeerListRow::stopLastRipple() { if (_ripple) { _ripple->lastStop(); } } -void PeerListBox::Row::paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth) { +void PeerListRow::paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth) { if (_ripple) { _ripple->paint(p, x, y, outerWidth, ms); if (_ripple->empty()) { @@ -330,7 +390,7 @@ void PeerListBox::Row::paintRipple(Painter &p, TimeMs ms, int x, int y, int oute } } -void PeerListBox::Row::paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth) { +void PeerListRow::paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth) { if (_checkbox) { if (disabled() && checked()) { paintDisabledCheckUserpic(p, x, y, outerWidth); @@ -343,7 +403,7 @@ void PeerListBox::Row::paintUserpic(Painter &p, TimeMs ms, int x, int y, int out } // Emulates Ui::RoundImageCheckbox::paint() in a checked state. -void PeerListBox::Row::paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const { +void PeerListRow::paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const { auto userpicRadius = st::contactsPhotoCheckbox.imageSmallRadius; auto userpicShift = st::contactsPhotoCheckbox.imageRadius - userpicRadius; auto userpicDiameter = st::contactsPhotoCheckbox.imageRadius * 2; @@ -377,11 +437,11 @@ void PeerListBox::Row::paintDisabledCheckUserpic(Painter &p, int x, int y, int o st::contactsPhotoCheckbox.check.check.paint(p, iconEllipse.topLeft(), outerWidth); } -float64 PeerListBox::Row::checkedRatio() { +float64 PeerListRow::checkedRatio() { return _checkbox ? _checkbox->checkedAnimationRatio() : 0.; } -void PeerListBox::Row::lazyInitialize() { +void PeerListRow::lazyInitialize() { if (_initialized) { return; } @@ -390,18 +450,18 @@ void PeerListBox::Row::lazyInitialize() { refreshStatus(); } -void PeerListBox::Row::createCheckbox(base::lambda updateCallback) { +void PeerListRow::createCheckbox(base::lambda updateCallback) { _checkbox = std::make_unique(st::contactsPhotoCheckbox, std::move(updateCallback), PaintUserpicCallback(_peer)); } -void PeerListBox::Row::setCheckedInternal(bool checked, SetStyle style) { +void PeerListRow::setCheckedInternal(bool checked, SetStyle style) { Expects(_checkbox != nullptr); using CheckboxStyle = Ui::RoundCheckbox::SetStyle; auto speed = (style == SetStyle::Animated) ? CheckboxStyle::Animated : CheckboxStyle::Fast; _checkbox->setChecked(checked, speed); } -PeerListBox::Inner::Inner(QWidget *parent, Controller *controller) : TWidget(parent) +PeerListBox::Inner::Inner(QWidget *parent, gsl::not_null controller) : TWidget(parent) , _controller(controller) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) { subscribe(AuthSession::CurrentDownloaderTaskFinished(), [this] { update(); }); @@ -416,7 +476,8 @@ PeerListBox::Inner::Inner(QWidget *parent, Controller *controller) : TWidget(par }); } -void PeerListBox::Inner::appendRow(std::unique_ptr row) { +void PeerListBox::Inner::appendRow(std::unique_ptr row) { + Expects(row != nullptr); if (_rowsById.find(row->id()) == _rowsById.cend()) { row->setAbsoluteIndex(_rows.size()); addRowEntry(row.get()); @@ -424,33 +485,42 @@ void PeerListBox::Inner::appendRow(std::unique_ptr row) { } } -void PeerListBox::Inner::appendGlobalSearchRow(std::unique_ptr row) { +void PeerListBox::Inner::appendSearchRow(std::unique_ptr row) { + Expects(row != nullptr); Expects(showingSearch()); if (_rowsById.find(row->id()) == _rowsById.cend()) { - row->setAbsoluteIndex(_globalSearchRows.size()); - row->setIsGlobalSearchResult(true); + row->setAbsoluteIndex(_searchRows.size()); + row->setIsSearchResult(true); addRowEntry(row.get()); _filterResults.push_back(row.get()); - _globalSearchRows.push_back(std::move(row)); + _searchRows.push_back(std::move(row)); } } -void PeerListBox::Inner::changeCheckState(Row *row, bool checked, Row::SetStyle style) { +void PeerListBox::Inner::appendFoundRow(gsl::not_null row) { + Expects(showingSearch()); + auto index = findRowIndex(row); + if (index.value < 0) { + _filterResults.push_back(row); + } +} + +void PeerListBox::Inner::changeCheckState(gsl::not_null row, bool checked, PeerListRow::SetStyle style) { row->setChecked(checked, style, [this, row] { updateRow(row); }); } -void PeerListBox::Inner::addRowEntry(Row *row) { +void PeerListBox::Inner::addRowEntry(gsl::not_null row) { _rowsById.emplace(row->id(), row); _rowsByPeer[row->peer()].push_back(row); if (addingToSearchIndex()) { addToSearchIndex(row); } - if (_searchMode != SearchMode::None) { + if (_searchMode != PeerListSearchMode::None) { t_assert(row->id() == row->peer()->id); - if (_controller->view()->isRowSelected(row->peer())) { - changeCheckState(row, true, Row::SetStyle::Fast); + if (_controller->isRowSelected(row->peer())) { + changeCheckState(row, true, PeerListRow::SetStyle::Fast); } } } @@ -459,18 +529,18 @@ void PeerListBox::Inner::invalidatePixmapsCache() { for_const (auto &row, _rows) { row->invalidatePixmapsCache(); } - for_const (auto &row, _globalSearchRows) { + for_const (auto &row, _searchRows) { row->invalidatePixmapsCache(); } } bool PeerListBox::Inner::addingToSearchIndex() const { // If we started indexing already, we continue. - return (_searchMode != SearchMode::None) || !_searchIndex.empty(); + return (_searchMode != PeerListSearchMode::None) || !_searchIndex.empty(); } -void PeerListBox::Inner::addToSearchIndex(Row *row) { - if (row->isGlobalSearchResult()) { +void PeerListBox::Inner::addToSearchIndex(gsl::not_null row) { + if (row->isSearchResult()) { return; } @@ -481,7 +551,7 @@ void PeerListBox::Inner::addToSearchIndex(Row *row) { } } -void PeerListBox::Inner::removeFromSearchIndex(Row *row) { +void PeerListBox::Inner::removeFromSearchIndex(gsl::not_null row) { auto &nameFirstChars = row->nameFirstChars(); if (!nameFirstChars.empty()) { for_const (auto ch, row->nameFirstChars()) { @@ -498,7 +568,8 @@ void PeerListBox::Inner::removeFromSearchIndex(Row *row) { } } -void PeerListBox::Inner::prependRow(std::unique_ptr row) { +void PeerListBox::Inner::prependRow(std::unique_ptr row) { + Expects(row != nullptr); if (_rowsById.find(row->id()) == _rowsById.cend()) { addRowEntry(row.get()); _rows.insert(_rows.begin(), std::move(row)); @@ -513,15 +584,15 @@ void PeerListBox::Inner::refreshIndices() { } } -PeerListBox::Row *PeerListBox::Inner::findRow(RowId id) { +PeerListRow *PeerListBox::Inner::findRow(PeerListRowId id) { auto it = _rowsById.find(id); - return (it == _rowsById.cend()) ? nullptr : it->second; + return (it == _rowsById.cend()) ? nullptr : it->second.get(); } -void PeerListBox::Inner::removeRow(Row *row) { +void PeerListBox::Inner::removeRow(gsl::not_null row) { auto index = row->absoluteIndex(); - auto isGlobalSearchResult = row->isGlobalSearchResult(); - auto &eraseFrom = isGlobalSearchResult ? _globalSearchRows : _rows; + auto isGlobalSearchResult = row->isSearchResult(); + auto &eraseFrom = isGlobalSearchResult ? _searchRows : _rows; t_assert(index >= 0 && index < eraseFrom.size()); t_assert(eraseFrom[index].get() == row); @@ -546,15 +617,29 @@ int PeerListBox::Inner::fullRowsCount() const { return _rows.size(); } -PeerListBox::Row *PeerListBox::Inner::rowAt(int index) const { +gsl::not_null PeerListBox::Inner::rowAt(int index) const { Expects(index >= 0 && index < _rows.size()); return _rows[index].get(); } -void PeerListBox::Inner::setAbout(object_ptr about) { - _about = std::move(about); - if (_about) { - _about->setParent(this); +void PeerListBox::Inner::setDescription(object_ptr description) { + _description = std::move(description); + if (_description) { + _description->setParent(this); + } +} + +void PeerListBox::Inner::setSearchLoading(object_ptr loading) { + _searchLoading = std::move(loading); + if (_searchLoading) { + _searchLoading->setParent(this); + } +} + +void PeerListBox::Inner::setSearchNoResults(object_ptr noResults) { + _searchNoResults = std::move(noResults); + if (_searchNoResults) { + _searchNoResults->setParent(this); } } @@ -569,36 +654,37 @@ int PeerListBox::Inner::labelHeight() const { if (!_filterResults.empty()) { return 0; } - if (globalSearchLoading()) { + if (_controller->isSearchLoading()) { return computeLabelHeight(_searchLoading); } return computeLabelHeight(_searchNoResults); } - return computeLabelHeight(_about); + return computeLabelHeight(_description); } void PeerListBox::Inner::refreshRows() { auto labelTop = st::membersMarginTop + qMax(1, shownRowsCount()) * _rowHeight; resize(width(), labelTop + labelHeight() + st::membersMarginBottom); - if (_about) { - _about->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top()); - _about->setVisible(!showingSearch()); + if (_description) { + _description->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top()); + _description->setVisible(!showingSearch()); } if (_searchNoResults) { _searchNoResults->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top()); - _searchNoResults->setVisible(showingSearch() && _filterResults.empty() && !globalSearchLoading()); + _searchNoResults->setVisible(showingSearch() && _filterResults.empty() && !_controller->isSearchLoading()); } if (_searchLoading) { _searchLoading->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top()); - _searchLoading->setVisible(showingSearch() && _filterResults.empty() && globalSearchLoading()); + _searchLoading->setVisible(showingSearch() && _filterResults.empty() && _controller->isSearchLoading()); } if (_visibleBottom > 0) { checkScrollForPreload(); } + updateSelection(); update(); } -void PeerListBox::Inner::setSearchMode(SearchMode mode) { +void PeerListBox::Inner::setSearchMode(PeerListSearchMode mode) { if (_searchMode != mode) { if (!addingToSearchIndex()) { for_const (auto &row, _rows) { @@ -606,33 +692,19 @@ void PeerListBox::Inner::setSearchMode(SearchMode mode) { } } _searchMode = mode; - if (_searchMode == SearchMode::Global) { + if (_searchMode == PeerListSearchMode::Complex) { if (!_searchLoading) { setSearchLoading(object_ptr(this, lang(lng_contacts_loading), Ui::FlatLabel::InitType::Simple, st::membersAbout)); } } else { - clearGlobalSearchRows(); + clearSearchRows(); } } } -void PeerListBox::Inner::clearGlobalSearchRows() { - while (!_globalSearchRows.empty()) { - removeRow(_globalSearchRows.back().get()); - } -} - -void PeerListBox::Inner::setSearchNoResults(object_ptr searchNoResults) { - _searchNoResults = std::move(searchNoResults); - if (_searchNoResults) { - _searchNoResults->setParent(this); - } -} - -void PeerListBox::Inner::setSearchLoading(object_ptr searchLoading) { - _searchLoading = std::move(searchLoading); - if (_searchLoading) { - _searchLoading->setParent(this); +void PeerListBox::Inner::clearSearchRows() { + while (!_searchRows.empty()) { + removeRow(_searchRows.back().get()); } } @@ -766,12 +838,16 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) { } p.setFont(st::contactsStatusFont); - if (row->isGlobalSearchResult() && !peer->userName().isEmpty()) { + if (row->isSearchResult() && !peer->userName().isEmpty()) { auto username = peer->userName(); - if (!_globalSearchHighlight.isEmpty() && username.startsWith(_globalSearchHighlight, Qt::CaseInsensitive)) { + auto mentionHighlight = _searchQuery; + if (mentionHighlight.startsWith('@')) { + mentionHighlight = mentionHighlight.mid(1); + } + if (!mentionHighlight.isEmpty() && username.startsWith(mentionHighlight, Qt::CaseInsensitive)) { auto availableWidth = width() - namex - st::contactsPadding.right(); - auto highlightedPart = '@' + username.mid(0, _globalSearchHighlight.size()); - auto grayedPart = username.mid(_globalSearchHighlight.size()); + auto highlightedPart = '@' + username.mid(0, mentionHighlight.size()); + auto grayedPart = username.mid(mentionHighlight.size()); auto highlightedWidth = st::contactsStatusFont->width(highlightedPart); if (highlightedWidth >= availableWidth || grayedPart.isEmpty()) { if (highlightedWidth > availableWidth) { @@ -807,7 +883,7 @@ void PeerListBox::Inner::selectSkip(int direction) { auto rowsCount = shownRowsCount(); auto index = 0; auto firstEnabled = -1, lastEnabled = -1; - enumerateShownRows([&firstEnabled, &lastEnabled, &index](Row *row) { + enumerateShownRows([&firstEnabled, &lastEnabled, &index](gsl::not_null row) { if (!row->disabled()) { if (firstEnabled < 0) { firstEnabled = index; @@ -909,9 +985,9 @@ void PeerListBox::Inner::searchQueryChanged(QString query) { _searchQuery = query; _filterResults.clear(); - clearGlobalSearchRows(); - if (!searchWordsList.isEmpty()) { - auto minimalList = (const std::vector*)nullptr; + clearSearchRows(); + if (_controller->searchInLocal() && !searchWordsList.isEmpty()) { + auto minimalList = (const std::vector>*)nullptr; for_const (auto &searchWord, searchWordsList) { auto searchWordStart = searchWord[0].toLower(); auto it = _searchIndex.find(searchWordStart); @@ -949,92 +1025,14 @@ void PeerListBox::Inner::searchQueryChanged(QString query) { } } } - if (_searchMode == SearchMode::Global) { - _globalSearchRequestId = 0; - needGlobalSearch(); + if (_searchMode == PeerListSearchMode::Complex) { + _controller->search(_searchQuery); } refreshRows(); restoreSelection(); } } -void PeerListBox::Inner::needGlobalSearch() { - if (!globalSearchInCache()) { - if (!_globalSearchTimer) { - _globalSearchTimer = object_ptr(this); - _globalSearchTimer->setTimeoutHandler([this] { globalSearchOnServer(); }); - } - _globalSearchTimer->start(AutoSearchTimeout); - } -} - -bool PeerListBox::Inner::globalSearchInCache() { - auto it = _globalSearchCache.find(_searchQuery); - if (it != _globalSearchCache.cend()) { - _globalSearchQuery = _searchQuery; - _globalSearchRequestId = 0; - globalSearchDone(it->second, _globalSearchRequestId); - return true; - } - return false; -} - -void PeerListBox::Inner::globalSearchOnServer() { - _globalSearchQuery = _searchQuery; - _globalSearchRequestId = request(MTPcontacts_Search(MTP_string(_globalSearchQuery), MTP_int(SearchPeopleLimit))).done([this](const MTPcontacts_Found &result, mtpRequestId requestId) { - globalSearchDone(result, requestId); - }).fail([this](const RPCError &error, mtpRequestId requestId) { - if (_globalSearchRequestId == requestId) { - _globalSearchRequestId = 0; - refreshRows(); - } - }).send(); - _globalSearchQueries.emplace(_globalSearchRequestId, _globalSearchQuery); -} - -void PeerListBox::Inner::globalSearchDone(const MTPcontacts_Found &result, mtpRequestId requestId) { - auto query = _globalSearchQuery; - if (requestId) { - auto it = _globalSearchQueries.find(requestId); - if (it != _globalSearchQueries.cend()) { - query = it->second; - _globalSearchCache[query] = result; - _globalSearchQueries.erase(it); - } - } - if (_globalSearchRequestId == requestId) { - _globalSearchRequestId = 0; - if (result.type() == mtpc_contacts_found) { - auto &contacts = result.c_contacts_found(); - App::feedUsers(contacts.vusers); - App::feedChats(contacts.vchats); - - _globalSearchHighlight = query; - if (!_globalSearchHighlight.isEmpty() && _globalSearchHighlight[0] == '@') { - _globalSearchHighlight = _globalSearchHighlight.mid(1); - } - - for_const (auto &mtpPeer, contacts.vresults.v) { - if (auto peer = App::peerLoaded(peerFromMTP(mtpPeer))) { - if (findRow(peer->id)) { - continue; - } - if (auto row = _controller->createGlobalRow(peer)) { - t_assert(row->id() == row->peer()->id); - appendGlobalSearchRow(std::move(row)); - } - } - } - } - refreshRows(); - updateSelection(); - } -} - -bool PeerListBox::Inner::globalSearchLoading() const { - return (_globalSearchTimer && _globalSearchTimer->isActive()) || _globalSearchRequestId; -} - void PeerListBox::Inner::submitted() { if (auto row = getRow(_selected.index)) { _controller->rowClicked(row); @@ -1084,7 +1082,7 @@ void PeerListBox::Inner::updateSelection() { setSelected(selected); } -QRect PeerListBox::Inner::getActionRect(Row *row, RowIndex index) const { +QRect PeerListBox::Inner::getActionRect(gsl::not_null row, RowIndex index) const { auto actionSize = row->actionSize(); if (actionSize.isEmpty()) { return QRect(); @@ -1108,7 +1106,7 @@ int PeerListBox::Inner::getRowTop(RowIndex index) const { return -1; } -void PeerListBox::Inner::updateRow(Row *row, RowIndex hint) { +void PeerListBox::Inner::updateRow(gsl::not_null row, RowIndex hint) { updateRow(findRowIndex(row, hint)); } @@ -1155,7 +1153,7 @@ bool PeerListBox::Inner::enumerateShownRows(int from, int to, Callback callback) return true; } -PeerListBox::Row *PeerListBox::Inner::getRow(RowIndex index) { +PeerListRow *PeerListBox::Inner::getRow(RowIndex index) { if (index.value >= 0) { if (showingSearch()) { if (index.value < _filterResults.size()) { @@ -1168,9 +1166,9 @@ PeerListBox::Row *PeerListBox::Inner::getRow(RowIndex index) { return nullptr; } -PeerListBox::Inner::RowIndex PeerListBox::Inner::findRowIndex(Row *row, RowIndex hint) { +PeerListBox::Inner::RowIndex PeerListBox::Inner::findRowIndex(gsl::not_null row, RowIndex hint) { if (!showingSearch()) { - t_assert(!row->isGlobalSearchResult()); + t_assert(!row->isSearchResult()); return RowIndex(row->absoluteIndex()); } @@ -1213,7 +1211,7 @@ void PeerListRowWithLink::refreshActionLink() { } void PeerListRowWithLink::lazyInitialize() { - Row::lazyInitialize(); + PeerListRow::lazyInitialize(); refreshActionLink(); } @@ -1231,9 +1229,78 @@ void PeerListRowWithLink::paintAction(Painter &p, TimeMs ms, int x, int y, int o p.drawTextLeft(x, y, outerWidth, _action, _actionWidth); } +PeerListGlobalSearchController::PeerListGlobalSearchController() { + _timer.setCallback([this] { searchOnServer(); }); +} + +void PeerListGlobalSearchController::searchQuery(const QString &query) { + _query = query; + _requestId = 0; + if (_query.isEmpty() && !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() { - view()->setSearchNoResultsText(lang(lng_blocked_list_not_found)); - view()->setSearchMode(PeerListBox::SearchMode::Global); + setSearchNoResultsText(lang(lng_blocked_list_not_found)); + delegate()->peerListSetSearchMode(PeerListSearchMode::Complex); prepareViewHook(); @@ -1253,7 +1320,7 @@ void ChatsListBoxController::prepare() { void ChatsListBoxController::rebuildRows() { auto ms = getms(); - auto wasEmpty = !view()->fullRowsCount(); + auto wasEmpty = !delegate()->peerListFullRowsCount(); auto appendList = [this](auto chats) { auto count = 0; for_const (auto row, chats->all()) { @@ -1269,39 +1336,37 @@ void ChatsListBoxController::rebuildRows() { auto added = appendList(App::main()->dialogsList()); added += appendList(App::main()->contactsNoDialogsList()); if (!wasEmpty && added > 0) { - view()->reorderRows([](auto &&begin, auto &&end) { - // Place dialogs list before contactsNoDialogs list. - std::stable_partition(begin, end, [](auto &row) { - auto history = static_cast(*row).history(); - return history->inChatList(Dialogs::Mode::All); - }); + // Place dialogs list before contactsNoDialogs list. + delegate()->peerListPartitionRows([](PeerListRow &a) { + auto history = static_cast(a).history(); + return history->inChatList(Dialogs::Mode::All); }); } checkForEmptyRows(); - view()->refreshRows(); + delegate()->peerListRefreshRows(); } void ChatsListBoxController::checkForEmptyRows() { - if (view()->fullRowsCount()) { - view()->setAboutText(QString()); + if (delegate()->peerListFullRowsCount()) { + setDescriptionText(QString()); } else { auto &sessionData = AuthSession::Current().data(); auto loaded = sessionData.contactsLoaded().value() && sessionData.allChatsLoaded().value(); - view()->setAboutText(lang(loaded ? lng_contacts_not_found : lng_contacts_loading)); + setDescriptionText(lang(loaded ? lng_contacts_not_found : lng_contacts_loading)); } } -std::unique_ptr ChatsListBoxController::createGlobalRow(PeerData *peer) { +std::unique_ptr ChatsListBoxController::createSearchRow(gsl::not_null peer) { return createRow(App::history(peer)); } bool ChatsListBoxController::appendRow(History *history) { - if (auto row = view()->findRow(history->peer->id)) { + if (auto row = delegate()->peerListFindRow(history->peer->id)) { updateRowHook(static_cast(row)); return false; } if (auto row = createRow(history)) { - view()->appendRow(std::move(row)); + 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 ba078eae47..e7f08733d9 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/abstract_box.h" #include "mtproto/sender.h" +#include "base/timer.h" namespace Ui { class RippleAnimation; @@ -32,216 +33,298 @@ class WidgetSlideWrap; class FlatLabel; } // namespace Ui -class PeerListBox : public BoxContent { +using PeerListRowId = uint64; +class PeerListRow { +public: + PeerListRow(gsl::not_null peer); + PeerListRow(gsl::not_null peer, PeerListRowId id); + + void setDisabled(bool disabled) { + _disabled = disabled; + } + + // Checked state is controlled by the box with multiselect, + // not by the row itself, so there is no setChecked() method. + // We can query the checked state from row, but before it is + // added to the box it is always false. + bool checked() const; + + gsl::not_null peer() const { + return _peer; + } + PeerListRowId id() const { + return _id; + } + + void setCustomStatus(const QString &status); + void clearCustomStatus(); + + virtual ~PeerListRow(); + + // Box interface. + virtual bool needsVerifiedIcon() const { + return _peer->isVerified(); + } + virtual QSize actionSize() const { + return QSize(); + } + virtual QMargins actionMargins() const { + return QMargins(); + } + virtual void addActionRipple(QPoint point, base::lambda updateCallback) { + } + virtual void stopLastActionRipple() { + } + virtual void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) { + } + + void refreshName(); + const Text &name() const { + return _name; + } + + enum class StatusType { + Online, + LastSeen, + Custom, + }; + void refreshStatus(); + + void setAbsoluteIndex(int index) { + _absoluteIndex = index; + } + int absoluteIndex() const { + return _absoluteIndex; + } + bool disabled() const { + return _disabled; + } + bool isSearchResult() const { + return _isSearchResult; + } + void setIsSearchResult(bool isSearchResult) { + _isSearchResult = isSearchResult; + } + + enum class SetStyle { + Animated, + Fast, + }; + template + void setChecked(bool checked, SetStyle style, UpdateCallback callback) { + if (checked && !_checkbox) { + createCheckbox(std::move(callback)); + } + setCheckedInternal(checked, style); + } + void invalidatePixmapsCache(); + + template + void addRipple(QSize size, QPoint point, UpdateCallback updateCallback); + void stopLastRipple(); + void paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth); + void paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth); + float64 checkedRatio(); + + void setNameFirstChars(const OrderedSet &nameFirstChars) { + _nameFirstChars = nameFirstChars; + } + const OrderedSet &nameFirstChars() const { + return _nameFirstChars; + } + + virtual void lazyInitialize(); + virtual void paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected); + +protected: + bool isInitialized() const { + return _initialized; + } + +private: + void createCheckbox(base::lambda updateCallback); + void setCheckedInternal(bool checked, SetStyle style); + void paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const; + + PeerListRowId _id = 0; + gsl::not_null _peer; + bool _initialized = false; + std::unique_ptr _ripple; + std::unique_ptr _checkbox; + Text _name; + QString _status; + StatusType _statusType = StatusType::Online; + bool _disabled = false; + int _absoluteIndex = -1; + OrderedSet _nameFirstChars; + bool _isSearchResult = false; + +}; + +enum class PeerListSearchMode { + None, + Local, + Complex, +}; + +class PeerListDelegate { +public: + virtual void peerListSetTitle(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; + virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0; + virtual void peerListAppendRow(std::unique_ptr row) = 0; + virtual void peerListAppendSearchRow(std::unique_ptr row) = 0; + virtual void peerListAppendFoundRow(gsl::not_null row) = 0; + virtual void peerListPrependRow(std::unique_ptr row) = 0; + virtual void peerListUpdateRow(gsl::not_null row) = 0; + virtual bool peerListIsRowSelected(gsl::not_null peer) = 0; + virtual void peerListRemoveRow(gsl::not_null row) = 0; + virtual void peerListSetRowChecked(gsl::not_null row, bool checked) = 0; + virtual gsl::not_null peerListRowAt(int index) = 0; + virtual void peerListRefreshRows() = 0; + virtual void peerListScrollToTop() = 0; + virtual int peerListFullRowsCount() = 0; + virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0; + virtual void peerListSortRows(base::lambda compare) = 0; + virtual void peerListPartitionRows(base::lambda border) = 0; + + template + void peerListAddSelectedRows(PeerDataRange &&range) { + for (auto peer : range) { + peerListAddSelectedRowInBunch(peer); + } + peerListFinishSelectedRowsBunch(); + } + + virtual std::vector> peerListCollectSelectedRows() = 0; + virtual ~PeerListDelegate() = default; + +private: + virtual void peerListAddSelectedRowInBunch(gsl::not_null peer) = 0; + virtual void peerListFinishSelectedRowsBunch() = 0; + +}; + +class PeerListSearchDelegate { +public: + virtual void peerListSearchAddRow(gsl::not_null peer) = 0; + virtual void peerListSearchRefreshRows() = 0; + virtual ~PeerListSearchDelegate() = default; + +}; + +class PeerListSearchController { +public: + virtual void searchQuery(const QString &query) = 0; + virtual bool isLoading() = 0; + virtual ~PeerListSearchController() = default; + + void setDelegate(gsl::not_null delegate) { + _delegate = delegate; + } + +protected: + gsl::not_null delegate() const { + return _delegate; + } + +private: + PeerListSearchDelegate *_delegate = nullptr; + +}; + +class PeerListController : public PeerListSearchDelegate { +public: + // Search works only with RowId == peer->id. + PeerListController(std::unique_ptr searchController = nullptr); + + void setDelegate(gsl::not_null delegate) { + _delegate = delegate; + prepare(); + } + + virtual void prepare() = 0; + virtual void rowClicked(gsl::not_null row) = 0; + virtual void rowActionClicked(gsl::not_null row) { + } + virtual void preloadRows() { + } + bool isSearchLoading() const { + return _searchController ? _searchController->isLoading() : false; + } + virtual std::unique_ptr createSearchRow(gsl::not_null peer) { + return std::unique_ptr(); + } + + bool isRowSelected(gsl::not_null peer) { + return delegate()->peerListIsRowSelected(peer); + } + + virtual bool searchInLocal() { + return true; + } + void search(const QString &query); + + void peerListSearchAddRow(gsl::not_null peer) override; + void peerListSearchRefreshRows() override; + + virtual ~PeerListController() = default; + +protected: + gsl::not_null delegate() const { + return _delegate; + } + + void setDescriptionText(const QString &text); + void setSearchLoadingText(const QString &text); + void setSearchNoResultsText(const QString &text); + void setDescription(object_ptr description) { + delegate()->peerListSetDescription(std::move(description)); + } + void setSearchLoading(object_ptr loading) { + delegate()->peerListSetSearchLoading(std::move(loading)); + } + void setSearchNoResults(object_ptr noResults) { + delegate()->peerListSetSearchNoResults(std::move(noResults)); + } + +private: + PeerListDelegate *_delegate = nullptr; + std::unique_ptr _searchController = nullptr; + +}; + +class PeerListBox : public BoxContent, public PeerListDelegate { class Inner; public: - using RowId = uint64; + PeerListBox(QWidget*, std::unique_ptr controller, base::lambda init); - class Row { - public: - Row(PeerData *peer); - Row(PeerData *peer, RowId id); - - void setDisabled(bool disabled) { - _disabled = disabled; - } - - // Checked state is controlled by the box with multiselect, - // not by the row itself, so there is no setChecked() method. - // We can query the checked state from row, but before it is - // added to the box it is always false. - bool checked() const; - - PeerData *peer() const { - return _peer; - } - RowId id() const { - return _id; - } - - void setCustomStatus(const QString &status); - void clearCustomStatus(); - - virtual ~Row(); - - protected: - virtual void paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected); - - bool isInitialized() const { - return _initialized; - } - virtual void lazyInitialize(); - - private: - // Inner interface. - friend class PeerListBox; - friend class Inner; - - virtual bool needsVerifiedIcon() const { - return _peer->isVerified(); - } - virtual QSize actionSize() const { - return QSize(); - } - virtual QMargins actionMargins() const { - return QMargins(); - } - virtual void addActionRipple(QPoint point, base::lambda updateCallback) { - } - virtual void stopLastActionRipple() { - } - virtual void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) { - } - - void refreshName(); - const Text &name() const { - return _name; - } - - enum class StatusType { - Online, - LastSeen, - Custom, - }; - void refreshStatus(); - - void setAbsoluteIndex(int index) { - _absoluteIndex = index; - } - int absoluteIndex() const { - return _absoluteIndex; - } - bool disabled() const { - return _disabled; - } - bool isGlobalSearchResult() const { - return _isGlobalSearchResult; - } - void setIsGlobalSearchResult(bool isGlobalSearchResult) { - _isGlobalSearchResult = isGlobalSearchResult; - } - - enum class SetStyle { - Animated, - Fast, - }; - template - void setChecked(bool checked, SetStyle style, UpdateCallback callback) { - if (checked && !_checkbox) { - createCheckbox(std::move(callback)); - } - setCheckedInternal(checked, style); - } - void invalidatePixmapsCache(); - - template - void addRipple(QSize size, QPoint point, UpdateCallback updateCallback); - void stopLastRipple(); - void paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth); - void paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth); - float64 checkedRatio(); - - void setNameFirstChars(const OrderedSet &nameFirstChars) { - _nameFirstChars = nameFirstChars; - } - const OrderedSet &nameFirstChars() const { - return _nameFirstChars; - } - - private: - void createCheckbox(base::lambda updateCallback); - void setCheckedInternal(bool checked, SetStyle style); - void paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const; - - RowId _id = 0; - PeerData *_peer = nullptr; - bool _initialized = false; - std::unique_ptr _ripple; - std::unique_ptr _checkbox; - Text _name; - QString _status; - StatusType _statusType = StatusType::Online; - bool _disabled = false; - int _absoluteIndex = -1; - OrderedSet _nameFirstChars; - bool _isGlobalSearchResult = false; - - }; - - class Controller { - public: - virtual void prepare() = 0; - virtual void rowClicked(Row *row) = 0; - virtual void rowActionClicked(Row *row) { - } - virtual void preloadRows() { - } - virtual std::unique_ptr createGlobalRow(PeerData *peer) { - return std::unique_ptr(); - } - - virtual ~Controller() = default; - - protected: - PeerListBox *view() const { - return _view; - } - - private: - void setView(PeerListBox *box) { - _view = box; - prepare(); - } - - PeerListBox *_view = nullptr; - - friend class PeerListBox; - friend class Inner; - - }; - PeerListBox(QWidget*, std::unique_ptr controller); - - // Interface for the controller. - void appendRow(std::unique_ptr row); - void prependRow(std::unique_ptr row); - Row *findRow(RowId id); - void updateRow(Row *row); - void removeRow(Row *row); - void setRowChecked(Row *row, bool checked); - int fullRowsCount() const; - Row *rowAt(int index) const; - void setAboutText(const QString &aboutText); - void setAbout(object_ptr about); - void refreshRows(); - - // Search works only with RowId == peer->id. - enum class SearchMode { - None, - Local, - Global, - }; - void setSearchMode(SearchMode mode); - void setSearchNoResultsText(const QString &noResultsText); - void setSearchNoResults(object_ptr searchNoResults); - void setSearchLoadingText(const QString &searchLoadingText); - void setSearchLoading(object_ptr searchLoading); - - template - void addSelectedRows(PeerDataRange &&range) { - Expects(_select != nullptr); - for (auto peer : range) { - addSelectItem(peer, Row::SetStyle::Fast); - } - finishSelectItemsBunch(); + void peerListSetTitle(base::lambda title) override { + setTitle(std::move(title)); } - QVector collectSelectedRows() const; - - // callback takes two iterators, like [](auto &begin, auto &end). - template - void reorderRows(ReorderCallback &&callback); - - bool isRowSelected(PeerData *peer) const; + void peerListSetDescription(object_ptr description) override; + void peerListSetSearchLoading(object_ptr loading) override; + void peerListSetSearchNoResults(object_ptr noResults) override; + void peerListSetSearchMode(PeerListSearchMode mode) override; + void peerListAppendRow(std::unique_ptr row) override; + void peerListAppendSearchRow(std::unique_ptr row) override; + void peerListAppendFoundRow(gsl::not_null row) override; + void peerListPrependRow(std::unique_ptr row) override; + void peerListUpdateRow(gsl::not_null row) override; + void peerListRemoveRow(gsl::not_null row) override; + void peerListSetRowChecked(gsl::not_null row, bool checked) override; + gsl::not_null peerListRowAt(int index) override; + bool peerListIsRowSelected(gsl::not_null peer) override; + std::vector> peerListCollectSelectedRows() override; + void peerListRefreshRows() override; + void peerListScrollToTop() override; + int peerListFullRowsCount() override; + PeerListRow *peerListFindRow(PeerListRowId id) override; + void peerListSortRows(base::lambda compare) override; + void peerListPartitionRows(base::lambda border) override; protected: void prepare() override; @@ -251,8 +334,12 @@ protected: void resizeEvent(QResizeEvent *e) override; private: - void addSelectItem(PeerData *peer, Row::SetStyle style); - void finishSelectItemsBunch(); + void peerListAddSelectedRowInBunch(gsl::not_null peer) override { + addSelectItem(peer, PeerListRow::SetStyle::Fast); + } + void peerListFinishSelectedRowsBunch() override; + + void addSelectItem(gsl::not_null peer, PeerListRow::SetStyle style); object_ptr> createMultiSelect(); int getTopScrollSkip() const; void updateScrollSkips(); @@ -262,16 +349,17 @@ private: QPointer _inner; - std::unique_ptr _controller; + std::unique_ptr _controller; + base::lambda _init; }; // This class is hold in header because it requires Qt preprocessing. -class PeerListBox::Inner : public TWidget, private MTP::Sender, private base::Subscriber { +class PeerListBox::Inner : public TWidget, private base::Subscriber { Q_OBJECT public: - Inner(QWidget *parent, Controller *controller); + Inner(QWidget *parent, gsl::not_null controller); void selectSkip(int direction); void selectSkipPage(int height, int direction); @@ -284,22 +372,24 @@ public: void submitted(); // Interface for the controller. - void appendRow(std::unique_ptr row); - void prependRow(std::unique_ptr row); - Row *findRow(RowId id); - void updateRow(Row *row) { + void appendRow(std::unique_ptr row); + void appendSearchRow(std::unique_ptr row); + void appendFoundRow(gsl::not_null row); + void prependRow(std::unique_ptr row); + PeerListRow *findRow(PeerListRowId id); + void updateRow(gsl::not_null row) { updateRow(row, RowIndex()); } - void removeRow(Row *row); + void removeRow(gsl::not_null row); int fullRowsCount() const; - Row *rowAt(int index) const; - void setAbout(object_ptr about); + gsl::not_null rowAt(int index) const; + void setDescription(object_ptr description); + void setSearchLoading(object_ptr loading); + void setSearchNoResults(object_ptr noResults); void refreshRows(); - void setSearchMode(SearchMode mode); - void setSearchNoResults(object_ptr searchNoResults); - void setSearchLoading(object_ptr searchLoading); - void changeCheckState(Row *row, bool checked, Row::SetStyle style); + void setSearchMode(PeerListSearchMode mode); + void changeCheckState(gsl::not_null row, bool checked, PeerListRow::SetStyle style); template void reorderRows(ReorderCallback &&callback) { @@ -327,7 +417,6 @@ protected: private: void refreshIndices(); - void appendGlobalSearchRow(std::unique_ptr row); void invalidatePixmapsCache(); @@ -370,19 +459,19 @@ private: void loadProfilePhotos(); void checkScrollForPreload(); - void updateRow(Row *row, RowIndex hint); + void updateRow(gsl::not_null row, RowIndex hint); void updateRow(RowIndex row); int getRowTop(RowIndex row) const; - Row *getRow(RowIndex element); - RowIndex findRowIndex(Row *row, RowIndex hint = RowIndex()); - QRect getActionRect(Row *row, RowIndex index) const; + PeerListRow *getRow(RowIndex element); + RowIndex findRowIndex(gsl::not_null row, RowIndex hint = RowIndex()); + QRect getActionRect(gsl::not_null row, RowIndex index) const; void paintRow(Painter &p, TimeMs ms, RowIndex index); - void addRowEntry(Row *row); - void addToSearchIndex(Row *row); + void addRowEntry(gsl::not_null row); + void addToSearchIndex(gsl::not_null row); bool addingToSearchIndex() const; - void removeFromSearchIndex(Row *row); + void removeFromSearchIndex(gsl::not_null row); bool showingSearch() const { return !_searchQuery.isEmpty(); } @@ -396,14 +485,11 @@ private: int labelHeight() const; - void needGlobalSearch(); - bool globalSearchInCache(); - void globalSearchOnServer(); - void globalSearchDone(const MTPcontacts_Found &result, mtpRequestId requestId); - bool globalSearchLoading() const; - void clearGlobalSearchRows(); + void clearSearchRows(); + + gsl::not_null _controller; + PeerListSearchMode _searchMode = PeerListSearchMode::None; - Controller *_controller = nullptr; int _rowHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; @@ -412,43 +498,30 @@ private: Selected _pressed; bool _mouseSelection = false; - std::vector> _rows; - std::map _rowsById; - std::map> _rowsByPeer; + std::vector> _rows; + std::map> _rowsById; + std::map>> _rowsByPeer; - SearchMode _searchMode = SearchMode::None; - std::map> _searchIndex; + std::map>> _searchIndex; QString _searchQuery; - std::vector _filterResults; + std::vector> _filterResults; - object_ptr _about = { nullptr }; + object_ptr _description = { nullptr }; object_ptr _searchNoResults = { nullptr }; object_ptr _searchLoading = { nullptr }; QPoint _lastMousePosition; - std::vector> _globalSearchRows; - object_ptr _globalSearchTimer = { nullptr }; - QString _globalSearchQuery; - QString _globalSearchHighlight; - mtpRequestId _globalSearchRequestId = 0; - std::map _globalSearchCache; - std::map _globalSearchQueries; + std::vector> _searchRows; }; -template -inline void PeerListBox::reorderRows(ReorderCallback &&callback) { - _inner->reorderRows(std::forward(callback)); -} - -class PeerListRowWithLink : public PeerListBox::Row { +class PeerListRowWithLink : public PeerListRow { public: - using Row::Row; + using PeerListRow::PeerListRow; void setActionLink(const QString &action); -protected: void lazyInitialize() override; private: @@ -462,25 +535,47 @@ private: }; -class ChatsListBoxController : public PeerListBox::Controller, protected base::Subscriber { +class PeerListGlobalSearchController : public PeerListSearchController, private MTP::Sender { public: + PeerListGlobalSearchController(); + + void searchQuery(const QString &query) override; + bool isLoading() override; + +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 createGlobalRow(PeerData *peer) override final; + std::unique_ptr createSearchRow(gsl::not_null peer) override final; protected: - class Row : public PeerListBox::Row { + class Row : public PeerListRow { public: - Row(History *history) : PeerListBox::Row(history->peer), _history(history) { + Row(gsl::not_null history) : PeerListRow(history->peer), _history(history) { } - History *history() const { + gsl::not_null history() const { return _history; } private: - History *_history = nullptr; + gsl::not_null _history; }; - virtual std::unique_ptr createRow(History *history) = 0; + virtual std::unique_ptr createRow(gsl::not_null history) = 0; virtual void prepareViewHook() = 0; virtual void updateRowHook(Row *row) { } diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index 1cf4027ae2..852c9a9155 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -36,7 +36,7 @@ constexpr auto kPerPageCount = 100; } // namespace -class BoxController::Row : public PeerListBox::Row { +class BoxController::Row : public PeerListRow { public: Row(HistoryItem *item); @@ -77,12 +77,10 @@ public: return _items.front()->id; } -protected: void paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected) override; void addActionRipple(QPoint point, base::lambda updateCallback) override; void stopLastActionRipple() override; -private: bool needsVerifiedIcon() const override { return false; } @@ -94,6 +92,7 @@ private: } void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) override; +private: void refreshStatus(); static Type ComputeType(HistoryItem *item); @@ -105,7 +104,7 @@ private: }; -BoxController::Row::Row(HistoryItem *item) : PeerListBox::Row(item->history()->peer, item->id) +BoxController::Row::Row(HistoryItem *item) : PeerListRow(item->history()->peer, item->id) , _items(1, item) , _date(item->date.date()) , _type(ComputeType(item)) { @@ -124,7 +123,7 @@ void BoxController::Row::paintStatusText(Painter &p, int x, int y, int outerWidt icon->paint(p, x + st::callArrowPosition.x(), y + st::callArrowPosition.y(), outerWidth); x += + st::callArrowPosition.x() + icon->width() + st::callArrowSkip; - PeerListBox::Row::paintStatusText(p, x, y, outerWidth, selected); + PeerListRow::paintStatusText(p, x, y, outerWidth, selected); } void BoxController::Row::paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) { @@ -188,12 +187,12 @@ void BoxController::prepare() { if (auto row = rowForItem(item)) { row->itemRemoved(item); if (!row->hasItems()) { - view()->removeRow(row); - if (!view()->fullRowsCount()) { + delegate()->peerListRemoveRow(row); + if (!delegate()->peerListFullRowsCount()) { refreshAbout(); } } - view()->refreshRows(); + delegate()->peerListRefreshRows(); } }); subscribe(Current().newServiceMessage(), [this](const FullMsgId &msgId) { @@ -202,10 +201,9 @@ void BoxController::prepare() { } }); - view()->setTitle(langFactory(lng_call_box_title)); - view()->addButton(langFactory(lng_close), [this] { view()->closeBox(); }); - view()->setAboutText(lang(lng_contacts_loading)); - view()->refreshRows(); + delegate()->peerListSetTitle(langFactory(lng_call_box_title)); + setDescriptionText(lang(lng_contacts_loading)); + delegate()->peerListRefreshRows(); preloadRows(); } @@ -240,16 +238,16 @@ void BoxController::preloadRows() { } void BoxController::refreshAbout() { - view()->setAboutText(view()->fullRowsCount() ? QString() : lang(lng_call_box_about)); + setDescriptionText(delegate()->peerListFullRowsCount() ? QString() : lang(lng_call_box_about)); } -void BoxController::rowClicked(PeerListBox::Row *row) { - auto itemsRow = static_cast(row); +void BoxController::rowClicked(gsl::not_null row) { + auto itemsRow = static_cast(row.get()); auto itemId = itemsRow->maxItemId(); Ui::showPeerHistoryAsync(row->peer()->id, itemId); } -void BoxController::rowActionClicked(PeerListBox::Row *row) { +void BoxController::rowActionClicked(gsl::not_null row) { auto user = row->peer()->asUser(); t_assert(user != nullptr); @@ -274,7 +272,7 @@ void BoxController::receivedCalls(const QVector &result) { } refreshAbout(); - view()->refreshRows(); + delegate()->peerListRefreshRows(); } bool BoxController::insertRow(HistoryItem *item, InsertWay way) { @@ -284,24 +282,22 @@ bool BoxController::insertRow(HistoryItem *item, InsertWay way) { return false; } } - (way == InsertWay::Append) ? view()->appendRow(createRow(item)) : view()->prependRow(createRow(item)); - view()->reorderRows([](auto &&begin, auto &&end) { - std::sort(begin, end, [](auto &a, auto &b) { - return static_cast(*a).maxItemId() > static_cast(*a).maxItemId(); - }); + (way == InsertWay::Append) ? delegate()->peerListAppendRow(createRow(item)) : delegate()->peerListPrependRow(createRow(item)); + delegate()->peerListSortRows([](PeerListRow &a, PeerListRow &b) { + return static_cast(a).maxItemId() > static_cast(b).maxItemId(); }); return true; } BoxController::Row *BoxController::rowForItem(HistoryItem *item) { - auto v = view(); - if (auto fullRowsCount = v->fullRowsCount()) { + auto v = delegate(); + if (auto fullRowsCount = v->peerListFullRowsCount()) { auto itemId = item->id; - auto lastRow = static_cast(v->rowAt(fullRowsCount - 1)); + auto lastRow = static_cast(v->peerListRowAt(fullRowsCount - 1).get()); if (itemId < lastRow->minItemId()) { return lastRow; } - auto firstRow = static_cast(v->rowAt(0)); + auto firstRow = static_cast(v->peerListRowAt(0).get()); if (itemId > firstRow->maxItemId()) { return firstRow; } @@ -313,18 +309,18 @@ BoxController::Row *BoxController::rowForItem(HistoryItem *item) { auto right = fullRowsCount; while (left + 1 < right) { auto middle = (right + left) / 2; - auto middleRow = static_cast(v->rowAt(middle)); + auto middleRow = static_cast(v->peerListRowAt(middle).get()); if (middleRow->maxItemId() >= itemId) { left = middle; } else { right = middle; } } - auto result = static_cast(v->rowAt(left)); + auto result = static_cast(v->peerListRowAt(left).get()); // Check for rowAt(left)->minItemId > itemId > rowAt(left + 1)->maxItemId. // In that case we sometimes need to return rowAt(left + 1), not rowAt(left). if (result->minItemId() > itemId && left + 1 < fullRowsCount) { - auto possibleResult = static_cast(v->rowAt(left + 1)); + auto possibleResult = static_cast(v->peerListRowAt(left + 1).get()); t_assert(possibleResult->maxItemId() < itemId); if (possibleResult->canAddItem(item)) { return possibleResult; @@ -335,7 +331,7 @@ BoxController::Row *BoxController::rowForItem(HistoryItem *item) { return nullptr; } -std::unique_ptr BoxController::createRow(HistoryItem *item) const { +std::unique_ptr BoxController::createRow(HistoryItem *item) const { auto row = std::make_unique(item); return std::move(row); } diff --git a/Telegram/SourceFiles/calls/calls_box_controller.h b/Telegram/SourceFiles/calls/calls_box_controller.h index 4746e0ebd9..36868d1d77 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.h +++ b/Telegram/SourceFiles/calls/calls_box_controller.h @@ -24,11 +24,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Calls { -class BoxController : public PeerListBox::Controller, private base::Subscriber, private MTP::Sender { +class BoxController : public PeerListController, private base::Subscriber, private MTP::Sender { public: void prepare() override; - void rowClicked(PeerListBox::Row *row) override; - void rowActionClicked(PeerListBox::Row *row) override; + void rowClicked(gsl::not_null row) override; + void rowActionClicked(gsl::not_null row) override; void preloadRows() override; private: @@ -43,7 +43,7 @@ private: Prepend, }; bool insertRow(HistoryItem *item, InsertWay way); - std::unique_ptr createRow(HistoryItem *item) const; + std::unique_ptr createRow(HistoryItem *item) const; MsgId _offsetId = 0; mtpRequestId _loadRequestId = 0; diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index 27814d9e8f..96c374a4fe 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp @@ -90,13 +90,13 @@ void GroupMembersWidget::restrictUser(gsl::not_null user) { } auto defaultRestricted = MegagroupInfo::Restricted { EditRestrictedBox::DefaultRights(megagroup) }; auto currentRights = megagroup->mgInfo->lastRestricted.value(user, defaultRestricted).rights; - Ui::show(Box(megagroup, user, currentRights, base::lambda_guarded(this, [this, megagroup, user](const MTPChannelBannedRights &rights) { + Ui::show(Box(megagroup, user, currentRights, [megagroup, user](const MTPChannelBannedRights &rights) { Ui::hideLayer(); - MTP::send(MTPchannels_EditBanned(megagroup->inputChannel, user->inputUser, rights), rpcDone(base::lambda_guarded(this, [this, megagroup, user, rights](const MTPUpdates &result) { + MTP::send(MTPchannels_EditBanned(megagroup->inputChannel, user->inputUser, rights), rpcDone([megagroup, user, rights](const MTPUpdates &result) { if (App::main()) App::main()->sentUpdatesReceived(result); megagroup->applyEditBanned(user, rights); - }))); - }))); + })); + })); } void GroupMembersWidget::removePeer(PeerData *selectedPeer) { diff --git a/Telegram/SourceFiles/profile/profile_block_settings.cpp b/Telegram/SourceFiles/profile/profile_block_settings.cpp index b8d1df2c67..14d7b879a7 100644 --- a/Telegram/SourceFiles/profile/profile_block_settings.cpp +++ b/Telegram/SourceFiles/profile/profile_block_settings.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/confirm_box.h" #include "boxes/contacts_box.h" #include "boxes/peer_list_box.h" +#include "boxes/edit_participant_box.h" #include "observer_peer.h" #include "auth_session.h" #include "mainwidget.h" @@ -39,33 +40,168 @@ namespace { constexpr auto kBlockedPerPage = 40; -class BlockedBoxController : public PeerListBox::Controller, private base::Subscriber, private MTP::Sender { +class BlockedBoxSearchController : public PeerListSearchController, private MTP::Sender { public: - BlockedBoxController(ChannelData *channel) : _channel(channel) { + BlockedBoxSearchController(gsl::not_null channel, bool restricted, gsl::not_null*> rights); + + void searchQuery(const QString &query) override; + bool isLoading() override; + +private: + bool searchInCache(); + void searchOnServer(); + void searchDone(const MTPchannels_ChannelParticipants &result, mtpRequestId requestId); + + gsl::not_null _channel; + bool _restricted = false; + gsl::not_null*> _rights; + + base::Timer _timer; + QString _query; + mtpRequestId _requestId = 0; + int _offset = 0; + bool _allLoaded = false; + std::map _cache; + std::map _queries; + +}; + +BlockedBoxSearchController::BlockedBoxSearchController(gsl::not_null channel, bool restricted, gsl::not_null*> rights) +: _channel(channel) +, _restricted(restricted) +, _rights(rights) { + _timer.setCallback([this] { searchOnServer(); }); +} + +void BlockedBoxSearchController::searchQuery(const QString &query) { + if (_query != query) { + _query = query; + _offset = 0; + _requestId = 0; + if (!_query.isEmpty() && !searchInCache()) { + _timer.callOnce(AutoSearchTimeout); + } else { + _timer.cancel(); + } + } +} + +bool BlockedBoxSearchController::searchInCache() { + auto it = _cache.find(_query); + if (it != _cache.cend()) { + _requestId = 0; + searchDone(it->second, _requestId); + return true; + } + return false; +} + +void BlockedBoxSearchController::searchOnServer() { + Expects(!_query.isEmpty()); + auto filter = _restricted ? MTP_channelParticipantsBanned(MTP_string(_query)) : MTP_channelParticipantsKicked(MTP_string(_query)); + _requestId = request(MTPchannels_GetParticipants(_channel->inputChannel, filter , MTP_int(_offset), MTP_int(kBlockedPerPage))).done([this](const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { + searchDone(result, requestId); + }).fail([this](const RPCError &error, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + _allLoaded = true; + delegate()->peerListSearchRefreshRows(); + } + }).send(); + _queries.emplace(_requestId, _query); +} + +void BlockedBoxSearchController::searchDone(const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { + Expects(result.type() == mtpc_channels_channelParticipants); + + auto &participants = result.c_channels_channelParticipants(); + auto query = _query; + if (requestId) { + App::feedUsers(participants.vusers); + auto it = _queries.find(requestId); + if (it != _queries.cend()) { + query = it->second; + _cache[query] = result; + _queries.erase(it); + } } + if (_requestId == requestId) { + _requestId = 0; + auto &list = participants.vparticipants.v; + if (list.isEmpty()) { + _allLoaded = true; + } else { + for_const (auto &participant, list) { + ++_offset; + if (participant.type() == mtpc_channelParticipantBanned) { + auto &banned = participant.c_channelParticipantBanned(); + auto userId = banned.vuser_id.v; + if (auto user = App::userLoaded(userId)) { + delegate()->peerListSearchAddRow(user); + (*_rights)[user] = banned.vbanned_rights; + } + } else { + LOG(("API Error: Non kicked participant got while requesting for kicked participants: %1").arg(participant.type())); + continue; + } + } + } + delegate()->peerListSearchRefreshRows(); + } +} + +bool BlockedBoxSearchController::isLoading() { + return _timer.isActive() || _requestId; +} + +class BlockedBoxController : public PeerListController, private base::Subscriber, private MTP::Sender, public base::enable_weak_from_this { +public: + BlockedBoxController(gsl::not_null channel, bool restricted); + void prepare() override; - void rowClicked(PeerListBox::Row *row) override; - void rowActionClicked(PeerListBox::Row *row) override; + void rowClicked(gsl::not_null row) override; + void rowActionClicked(gsl::not_null row) override; void preloadRows() override; + bool searchInLocal() override { + return false; + } + + void peerListSearchAddRow(gsl::not_null peer) override; private: bool appendRow(UserData *user); bool prependRow(UserData *user); - std::unique_ptr createRow(UserData *user) const; + std::unique_ptr createRow(UserData *user) const; - ChannelData *_channel = nullptr; + gsl::not_null _channel; + bool _restricted = false; int _offset = 0; mtpRequestId _loadRequestId = 0; bool _allLoaded = false; + std::map _rights; + QPointer _editBox; }; +void BlockedBoxController::peerListSearchAddRow(gsl::not_null peer) { + PeerListController::peerListSearchAddRow(peer); + if (_restricted && delegate()->peerListFullRowsCount() > 0) { + setDescriptionText(QString()); + } +} + +BlockedBoxController::BlockedBoxController(gsl::not_null channel, bool restricted) : PeerListController(std::make_unique(channel, restricted, &_rights)) +, _channel(channel) +, _restricted(restricted) { +} + void BlockedBoxController::prepare() { - view()->setTitle(langFactory(lng_blocked_list_title)); - view()->addButton(langFactory(lng_close), [this] { view()->closeBox(); }); - view()->setAboutText(lang(lng_contacts_loading)); - view()->refreshRows(); + delegate()->peerListSetSearchMode(PeerListSearchMode::Complex); + delegate()->peerListSetTitle(langFactory(_restricted ? lng_restricted_list_title : lng_blocked_list_title)); + setDescriptionText(lang(lng_contacts_loading)); + setSearchNoResultsText(lang(lng_blocked_list_not_found)); + delegate()->peerListRefreshRows(); preloadRows(); } @@ -75,13 +211,14 @@ void BlockedBoxController::preloadRows() { return; } - _loadRequestId = request(MTPchannels_GetParticipants(_channel->inputChannel, MTP_channelParticipantsKicked(MTP_string("")), MTP_int(_offset), MTP_int(kBlockedPerPage))).done([this](const MTPchannels_ChannelParticipants &result) { + auto filter = _restricted ? MTP_channelParticipantsBanned(MTP_string(QString())) : MTP_channelParticipantsKicked(MTP_string(QString())); + _loadRequestId = request(MTPchannels_GetParticipants(_channel->inputChannel, filter, MTP_int(_offset), MTP_int(kBlockedPerPage))).done([this](const MTPchannels_ChannelParticipants &result) { Expects(result.type() == mtpc_channels_channelParticipants); _loadRequestId = 0; if (!_offset) { - view()->setAboutText(lang(lng_group_blocked_list_about)); + setDescriptionText(_restricted ? QString() : lang(lng_group_blocked_list_about)); } auto &participants = result.c_channels_channelParticipants(); App::feedUsers(participants.vusers); @@ -92,69 +229,90 @@ void BlockedBoxController::preloadRows() { } else { for_const (auto &participant, list) { ++_offset; - if (participant.type() != mtpc_channelParticipantBanned) { - LOG(("API Error: Non banned participant got while requesting for kicked participants: %1").arg(participant.type())); + if (participant.type() == mtpc_channelParticipantBanned) { + auto &banned = participant.c_channelParticipantBanned(); + auto userId = banned.vuser_id.v; + if (auto user = App::userLoaded(userId)) { + appendRow(user); + _rights[user] = banned.vbanned_rights; + } + } else { + LOG(("API Error: Non kicked participant got while requesting for kicked participants: %1").arg(participant.type())); continue; } - auto &kicked = participant.c_channelParticipantBanned(); - if (!kicked.is_left()) { - LOG(("API Error: Non left participant got while requesting for kicked participants.")); - continue; - } - auto userId = kicked.vuser_id.v; - if (auto user = App::userLoaded(userId)) { - appendRow(user); - } } } - view()->refreshRows(); + delegate()->peerListRefreshRows(); }).fail([this](const RPCError &error) { _loadRequestId = 0; }).send(); } -void BlockedBoxController::rowClicked(PeerListBox::Row *row) { +void BlockedBoxController::rowClicked(gsl::not_null row) { Ui::showPeerHistoryAsync(row->peer()->id, ShowAtUnreadMsgId); } -void BlockedBoxController::rowActionClicked(PeerListBox::Row *row) { +void BlockedBoxController::rowActionClicked(gsl::not_null row) { auto user = row->peer()->asUser(); Expects(user != nullptr); - view()->removeRow(row); - view()->refreshRows(); + if (_restricted) { + auto it = _rights.find(user); + t_assert(it != _rights.cend()); + auto weak = base::weak_unique_ptr(this); + _editBox = Ui::show(Box(_channel, user, it->second, [megagroup = _channel.get(), user, weak](const MTPChannelBannedRights &rights) { + MTP::send(MTPchannels_EditBanned(megagroup->inputChannel, user->inputUser, rights), rpcDone([megagroup, user, weak, rights](const MTPUpdates &result) { + if (App::main()) App::main()->sentUpdatesReceived(result); + megagroup->applyEditBanned(user, rights); + if (weak) { + weak->_editBox->closeBox(); + if (rights.c_channelBannedRights().vflags.v == 0 || rights.c_channelBannedRights().is_view_messages()) { + if (auto row = weak->delegate()->peerListFindRow(user->id)) { + weak->delegate()->peerListRemoveRow(row); + weak->delegate()->peerListRefreshRows(); + if (!weak->delegate()->peerListFullRowsCount()) { + weak->setDescriptionText(lang(lng_blocked_list_not_found)); + } + } + } else { + weak->_rights[user] = rights; + } + } + })); + }), KeepOtherLayers); + } else { + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); - AuthSession::Current().api().unblockParticipant(_channel, user); + AuthSession::Current().api().unblockParticipant(_channel, user); + } } bool BlockedBoxController::appendRow(UserData *user) { - if (view()->findRow(user->id)) { + if (delegate()->peerListFindRow(user->id)) { return false; } - view()->appendRow(createRow(user)); + delegate()->peerListAppendRow(createRow(user)); + if (_restricted) { + setDescriptionText(QString()); + } return true; } bool BlockedBoxController::prependRow(UserData *user) { - if (view()->findRow(user->id)) { + if (delegate()->peerListFindRow(user->id)) { return false; } - view()->prependRow(createRow(user)); + delegate()->peerListPrependRow(createRow(user)); + if (_restricted) { + setDescriptionText(QString()); + } return true; } -std::unique_ptr BlockedBoxController::createRow(UserData *user) const { +std::unique_ptr BlockedBoxController::createRow(UserData *user) const { auto row = std::make_unique(user); - row->setActionLink(lang(lng_blocked_list_unblock)); - auto status = [user]() -> QString { - if (user->botInfo) { - return lang(lng_status_bot); - } else if (user->phone().isEmpty()) { - return lang(lng_blocked_list_unknown_phone); - } - return App::formatPhone(user->phone()); - }; - row->setCustomStatus(status()); + row->setActionLink(lang(_restricted ? lng_profile_edit_permissions : lng_blocked_list_unblock)); return std::move(row); } @@ -223,6 +381,7 @@ int SettingsWidget::resizeGetHeight(int newWidth) { }; moveLink(_manageAdmins); moveLink(_manageBlockedUsers); + moveLink(_manageRestrictedUsers); moveLink(_inviteLink); newHeight += st::profileBlockMarginBottom; @@ -266,7 +425,7 @@ void SettingsWidget::refreshManageAdminsButton() { void SettingsWidget::refreshManageBlockedUsersButton() { auto hasManageBlockedUsers = [this] { if (auto channel = peer()->asMegagroup()) { - return channel->canBanMembers() && (channel->kickedCount() > 0 || channel->restrictedCount() > 0); + return channel->canBanMembers() && (channel->kickedCount() > 0); } return false; }; @@ -276,6 +435,19 @@ void SettingsWidget::refreshManageBlockedUsersButton() { _manageBlockedUsers->show(); connect(_manageBlockedUsers, SIGNAL(clicked()), this, SLOT(onManageBlockedUsers())); } + + auto hasManageRestrictedUsers = [this] { + if (auto channel = peer()->asMegagroup()) { + return channel->canBanMembers() && (channel->restrictedCount() > 0); + } + return false; + }; + _manageRestrictedUsers.destroy(); + if (hasManageRestrictedUsers()) { + _manageRestrictedUsers.create(this, lang(lng_profile_manage_restrictedlist), st::defaultLeftOutlineButton); + _manageRestrictedUsers->show(); + connect(_manageRestrictedUsers, SIGNAL(clicked()), this, SLOT(onManageRestrictedUsers())); + } } void SettingsWidget::refreshInviteLinkButton() { @@ -315,7 +487,17 @@ void SettingsWidget::onManageAdmins() { void SettingsWidget::onManageBlockedUsers() { if (auto channel = peer()->asMegagroup()) { - Ui::show(Box(std::make_unique(channel))); + Ui::show(Box(std::make_unique(channel, false), [](PeerListBox *box) { + box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); + })); + } +} + +void SettingsWidget::onManageRestrictedUsers() { + if (auto channel = peer()->asMegagroup()) { + Ui::show(Box(std::make_unique(channel, true), [](PeerListBox *box) { + box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); + })); } } diff --git a/Telegram/SourceFiles/profile/profile_block_settings.h b/Telegram/SourceFiles/profile/profile_block_settings.h index 0c9fb74236..cb958f9330 100644 --- a/Telegram/SourceFiles/profile/profile_block_settings.h +++ b/Telegram/SourceFiles/profile/profile_block_settings.h @@ -47,6 +47,7 @@ private slots: void onNotificationsChange(); void onManageAdmins(); void onManageBlockedUsers(); + void onManageRestrictedUsers(); void onInviteLink(); private: @@ -65,6 +66,7 @@ private: // In channels: creator of supergroup can see this link. object_ptr _manageAdmins = { nullptr }; object_ptr _manageBlockedUsers = { nullptr }; + object_ptr _manageRestrictedUsers = { nullptr }; object_ptr _inviteLink = { nullptr }; }; diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 2164df7522..db77713c63 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -35,36 +35,40 @@ constexpr auto kBlockedPerPage = 40; class BlockUserBoxController : public ChatsListBoxController { public: - void rowClicked(PeerListBox::Row *row) override; + void rowClicked(gsl::not_null row) override; + + void setBlockUserCallback(base::lambda user)> callback) { + _blockUserCallback = std::move(callback); + } protected: void prepareViewHook() override; - std::unique_ptr createRow(History *history) override; + std::unique_ptr createRow(gsl::not_null history) override; void updateRowHook(Row *row) override { updateIsBlocked(row, row->peer()->asUser()); - view()->updateRow(row); + delegate()->peerListUpdateRow(row); } private: - void updateIsBlocked(PeerListBox::Row *row, UserData *user) const; + void updateIsBlocked(gsl::not_null row, UserData *user) const; + + base::lambda user)> _blockUserCallback; }; void BlockUserBoxController::prepareViewHook() { - view()->setTitle(langFactory(lng_blocked_list_add_title)); - view()->addButton(langFactory(lng_cancel), [this] { view()->closeBox(); }); - + delegate()->peerListSetTitle(langFactory(lng_blocked_list_add_title)); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::UserIsBlocked, [this](const Notify::PeerUpdate &update) { if (auto user = update.peer->asUser()) { - if (auto row = view()->findRow(user->id)) { + if (auto row = delegate()->peerListFindRow(user->id)) { updateIsBlocked(row, user); - view()->updateRow(row); + delegate()->peerListUpdateRow(row); } } })); } -void BlockUserBoxController::updateIsBlocked(PeerListBox::Row *row, UserData *user) const { +void BlockUserBoxController::updateIsBlocked(gsl::not_null row, UserData *user) const { auto blocked = user->isBlocked(); row->setDisabled(blocked); if (blocked) { @@ -74,15 +78,11 @@ void BlockUserBoxController::updateIsBlocked(PeerListBox::Row *row, UserData *us } } -void BlockUserBoxController::rowClicked(PeerListBox::Row *row) { - auto user = row->peer()->asUser(); - Expects(user != nullptr); - - App::api()->blockUser(user); - view()->closeBox(); +void BlockUserBoxController::rowClicked(gsl::not_null row) { + _blockUserCallback(row->peer()->asUser()); } -std::unique_ptr BlockUserBoxController::createRow(History *history) { +std::unique_ptr BlockUserBoxController::createRow(gsl::not_null history) { if (auto user = history->peer->asUser()) { auto row = std::make_unique(history); updateIsBlocked(row.get(), user); @@ -94,11 +94,9 @@ std::unique_ptr BlockUserBoxController::createRow(H } // namespace void BlockedBoxController::prepare() { - view()->setTitle(langFactory(lng_blocked_list_title)); - view()->addButton(langFactory(lng_close), [this] { view()->closeBox(); }); - view()->addLeftButton(langFactory(lng_blocked_list_add), [this] { blockUser(); }); - view()->setAboutText(lang(lng_contacts_loading)); - view()->refreshRows(); + delegate()->peerListSetTitle(langFactory(lng_blocked_list_title)); + setDescriptionText(lang(lng_contacts_loading)); + delegate()->peerListRefreshRows(); subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::UserIsBlocked, [this](const Notify::PeerUpdate &update) { if (auto user = update.peer->asUser()) { @@ -118,7 +116,7 @@ void BlockedBoxController::preloadRows() { _loadRequestId = 0; if (!_offset) { - view()->setAboutText(lang(lng_blocked_list_about)); + setDescriptionText(lang(lng_blocked_list_about)); } auto handleContactsBlocked = [](auto &list) { @@ -140,11 +138,11 @@ void BlockedBoxController::preloadRows() { }).send(); } -void BlockedBoxController::rowClicked(PeerListBox::Row *row) { +void BlockedBoxController::rowClicked(gsl::not_null row) { Ui::showPeerHistoryAsync(row->peer()->id, ShowAtUnreadMsgId); } -void BlockedBoxController::rowActionClicked(PeerListBox::Row *row) { +void BlockedBoxController::rowActionClicked(gsl::not_null row) { auto user = row->peer()->asUser(); Expects(user != nullptr); @@ -168,42 +166,50 @@ void BlockedBoxController::receivedUsers(const QVector &resul user->setBlockStatus(UserData::BlockStatus::Blocked); } } - view()->refreshRows(); + delegate()->peerListRefreshRows(); } void BlockedBoxController::handleBlockedEvent(UserData *user) { if (user->isBlocked()) { if (prependRow(user)) { - view()->refreshRows(); - view()->onScrollToY(0); + delegate()->peerListRefreshRows(); + delegate()->peerListScrollToTop(); } - } else if (auto row = view()->findRow(user->id)) { - view()->removeRow(row); - view()->refreshRows(); + } else if (auto row = delegate()->peerListFindRow(user->id)) { + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); } } -void BlockedBoxController::blockUser() { - Ui::show(Box(std::make_unique()), KeepOtherLayers); +void BlockedBoxController::BlockNewUser() { + auto controller = std::make_unique(); + auto initBox = [controller = controller.get()](PeerListBox *box) { + controller->setBlockUserCallback([box](gsl::not_null user) { + App::api()->blockUser(user); + box->closeBox(); + }); + box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); + }; + Ui::show(Box(std::move(controller), std::move(initBox)), KeepOtherLayers); } bool BlockedBoxController::appendRow(UserData *user) { - if (view()->findRow(user->id)) { + if (delegate()->peerListFindRow(user->id)) { return false; } - view()->appendRow(createRow(user)); + delegate()->peerListAppendRow(createRow(user)); return true; } bool BlockedBoxController::prependRow(UserData *user) { - if (view()->findRow(user->id)) { + if (delegate()->peerListFindRow(user->id)) { return false; } - view()->prependRow(createRow(user)); + delegate()->peerListPrependRow(createRow(user)); return true; } -std::unique_ptr BlockedBoxController::createRow(UserData *user) const { +std::unique_ptr BlockedBoxController::createRow(UserData *user) const { auto row = std::make_unique(user); row->setActionLink(lang(lng_blocked_list_unblock)); auto status = [user]() -> QString { diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.h b/Telegram/SourceFiles/settings/settings_privacy_controllers.h index b3bc26d555..f172c2a492 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.h +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.h @@ -26,21 +26,22 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Settings { -class BlockedBoxController : public PeerListBox::Controller, private base::Subscriber, private MTP::Sender { +class BlockedBoxController : public PeerListController, private base::Subscriber, private MTP::Sender { public: void prepare() override; - void rowClicked(PeerListBox::Row *row) override; - void rowActionClicked(PeerListBox::Row *row) override; + void rowClicked(gsl::not_null row) override; + void rowActionClicked(gsl::not_null row) override; void preloadRows() override; + static void BlockNewUser(); + private: void receivedUsers(const QVector &result); void handleBlockedEvent(UserData *user); - void blockUser(); bool appendRow(UserData *user); bool prependRow(UserData *user); - std::unique_ptr createRow(UserData *user) const; + std::unique_ptr createRow(UserData *user) const; int _offset = 0; mtpRequestId _loadRequestId = 0; diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp index 86843f16bb..9ab61c1529 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp @@ -208,7 +208,10 @@ void PrivacyWidget::autoLockUpdated() { } void PrivacyWidget::onBlockedUsers() { - Ui::show(Box(std::make_unique())); + Ui::show(Box(std::make_unique(), [](PeerListBox *box) { + box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); + box->addLeftButton(langFactory(lng_blocked_list_add), [box] { BlockedBoxController::BlockNewUser(); }); + })); } void PrivacyWidget::onLastSeenPrivacy() { diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 000623dc98..bf49302c9e 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -86,7 +86,9 @@ void MainMenu::refreshMenu() { }, &st::mainMenuContacts, &st::mainMenuContactsOver); if (Global::PhoneCallsEnabled()) { _menu->addAction(lang(lng_menu_calls), [] { - Ui::show(Box(std::make_unique())); + Ui::show(Box(std::make_unique(), [](PeerListBox *box) { + box->addButton(langFactory(lng_close), [box] { box->closeBox(); }); + })); }, &st::mainMenuCalls, &st::mainMenuCallsOver); } _menu->addAction(lang(lng_menu_settings), [] {