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.
This commit is contained in:
John Preston 2017-06-14 00:08:47 +03:00
parent 5c0a1bafe2
commit 7fdac9cd94
15 changed files with 1062 additions and 702 deletions

View File

@ -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";

View File

@ -33,51 +33,50 @@ namespace {
class PrivacyExceptionsBoxController : public ChatsListBoxController {
public:
PrivacyExceptionsBoxController(base::lambda<QString()> titleFactory, const QVector<UserData*> &selected, base::lambda_once<void(QVector<UserData*> &&result)> saveCallback);
void rowClicked(PeerListBox::Row *row) override;
PrivacyExceptionsBoxController(base::lambda<QString()> titleFactory, const std::vector<gsl::not_null<UserData*>> &selected);
void rowClicked(gsl::not_null<PeerListRow*> row) override;
std::vector<gsl::not_null<UserData*>> getResult() const;
protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(History *history) override;
std::unique_ptr<Row> createRow(gsl::not_null<History*> history) override;
private:
base::lambda<QString()> _titleFactory;
QVector<UserData*> _selected;
base::lambda_once<void(QVector<UserData*> &&result)> _saveCallback;
std::vector<gsl::not_null<UserData*>> _selected;
};
PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(base::lambda<QString()> titleFactory, const QVector<UserData*> &selected, base::lambda_once<void(QVector<UserData*> &&result)> saveCallback)
PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(base::lambda<QString()> titleFactory, const std::vector<gsl::not_null<UserData*>> &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<UserData*>();
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<gsl::not_null<UserData*>> PrivacyExceptionsBoxController::getResult() const {
auto peers = delegate()->peerListCollectSelectedRows();
auto users = std::vector<gsl::not_null<UserData*>>();
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<PeerListRow*> row) {
delegate()->peerListSetRowChecked(row, !row->checked());
}
std::unique_ptr<PrivacyExceptionsBoxController::Row> PrivacyExceptionsBoxController::createRow(History *history) {
std::unique_ptr<PrivacyExceptionsBoxController::Row> PrivacyExceptionsBoxController::createRow(gsl::not_null<History*> history) {
if (auto user = history->peer->asUser()) {
if (!user->isSelf()) {
return std::make_unique<Row>(history);
@ -176,29 +175,35 @@ int EditPrivacyBox::countDefaultHeight(int newWidth) {
void EditPrivacyBox::editExceptionUsers(Exception exception) {
auto controller = std::make_unique<PrivacyExceptionsBoxController>(base::lambda_guarded(this, [this, exception] {
return _controller->exceptionBoxTitle(exception);
}), exceptionUsers(exception), base::lambda_guarded(this, [this, exception](QVector<UserData*> &&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<PeerListBox>(std::move(controller)), KeepOtherLayers);
box->closeBox();
}));
box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); });
};
Ui::show(Box<PeerListBox>(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<UserData*> &EditPrivacyBox::exceptionUsers(Exception exception) {
std::vector<gsl::not_null<UserData*>> &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);
}
}

View File

@ -102,7 +102,7 @@ private:
void editExceptionUsers(Exception exception);
QString exceptionLinkText(Exception exception);
QVector<UserData*> &exceptionUsers(Exception exception);
std::vector<gsl::not_null<UserData*>> &exceptionUsers(Exception exception);
object_ptr<Ui::WidgetSlideWrap<Ui::LinkButton>> &exceptionLink(Exception exception);
std::unique_ptr<Controller> _controller;
@ -120,7 +120,7 @@ private:
object_ptr<Ui::WidgetSlideWrap<Ui::LinkButton>> _neverLink = { nullptr };
object_ptr<Ui::FlatLabel> _exceptionsDescription = { nullptr };
QVector<UserData*> _alwaysUsers;
QVector<UserData*> _neverUsers;
std::vector<gsl::not_null<UserData*>> _alwaysUsers;
std::vector<gsl::not_null<UserData*>> _neverUsers;
};

View File

@ -163,7 +163,7 @@ MembersBox::Inner::Inner(QWidget *parent, gsl::not_null<ChannelData*> 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) {

View File

@ -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)
: _controller(std::move(controller)) {
PeerListBox::PeerListBox(QWidget*, std::unique_ptr<PeerListController> controller, base::lambda<void(PeerListBox*)> init)
: _controller(std::move(controller))
, _init(std::move(init)) {
Expects(_controller != nullptr);
}
object_ptr<Ui::WidgetSlideWrap<Ui::MultiSelect>> PeerListBox::createMultiSelect() {
@ -63,7 +65,7 @@ void PeerListBox::updateScrollSkips() {
void PeerListBox::prepare() {
_inner = setInnerWidget(object_ptr<Inner>(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> row) {
void PeerListBox::peerListAppendRow(std::unique_ptr<PeerListRow> row) {
_inner->appendRow(std::move(row));
}
void PeerListBox::prependRow(std::unique_ptr<Row> row) {
void PeerListBox::peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) {
_inner->appendSearchRow(std::move(row));
}
void PeerListBox::peerListAppendFoundRow(gsl::not_null<PeerListRow*> row) {
_inner->appendFoundRow(row);
}
void PeerListBox::peerListPrependRow(std::unique_ptr<PeerListRow> 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<PeerListRow*> row) {
_inner->updateRow(row);
}
void PeerListBox::removeRow(Row *row) {
void PeerListBox::peerListRemoveRow(gsl::not_null<PeerListRow*> row) {
_inner->removeRow(row);
}
void PeerListBox::setRowChecked(Row *row, bool checked) {
void PeerListBox::peerListSetRowChecked(gsl::not_null<PeerListRow*> 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<PeerListRow*> PeerListBox::peerListRowAt(int index) {
return _inner->rowAt(index);
}
void PeerListBox::setAboutText(const QString &aboutText) {
if (aboutText.isEmpty()) {
setAbout(nullptr);
} else {
setAbout(object_ptr<Ui::FlatLabel>(this, aboutText, Ui::FlatLabel::InitType::Simple, st::membersAbout));
}
}
void PeerListBox::setAbout(object_ptr<Ui::FlatLabel> 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<Ui::FlatLabel> description) {
_inner->setDescription(std::move(description));
}
void PeerListBox::peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) {
_inner->setSearchLoading(std::move(loading));
}
void PeerListBox::peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> 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<bool(PeerListRow &a, PeerListRow &b)> 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<bool(PeerListRow &a)> border) {
_inner->reorderRows([border = std::move(border)](auto &&begin, auto &&end) {
std::stable_partition(begin, end, [&border](auto &&current) {
return border(*current);
});
});
}
PeerListController::PeerListController(std::unique_ptr<PeerListSearchController> 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<PeerData*> 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<Ui::FlatLabel>(this, searchNoResultsText, Ui::FlatLabel::InitType::Simple, st::membersAbout));
setDescription(object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout));
}
}
void PeerListBox::setSearchNoResults(object_ptr<Ui::FlatLabel> 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<Ui::FlatLabel>(this, searchLoadingText, Ui::FlatLabel::InitType::Simple, st::membersAbout));
setSearchLoading(object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout));
}
}
void PeerListBox::setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading) {
_inner->setSearchLoading(std::move(searchLoading));
void PeerListController::setSearchNoResultsText(const QString &text) {
if (text.isEmpty()) {
setSearchNoResults(nullptr);
} else {
setSearchNoResults(object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout));
}
}
QVector<PeerData*> PeerListBox::collectSelectedRows() const {
void PeerListBox::addSelectItem(gsl::not_null<PeerData*> peer, PeerListRow::SetStyle style) {
Expects(_select != nullptr);
auto result = QVector<PeerData*>();
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<PeerData*> peer) {
Expects(_select != nullptr);
return _select->entity()->hasItem(peer->id);
}
std::vector<gsl::not_null<PeerData*>> PeerListBox::peerListCollectSelectedRows()
{
Expects(_select != nullptr);
auto result = std::vector<gsl::not_null<PeerData*>>();
auto items = _select->entity()->getItems();
if (!items.empty()) {
result.reserve(items.size());
@ -235,46 +314,27 @@ QVector<PeerData*> 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<PeerData*> peer) : PeerListRow(peer, peer->id) {
}
void PeerListBox::finishSelectItemsBunch() {
Expects(_select != nullptr);
_select->entity()->finishItemsBunch();
PeerListRow::PeerListRow(gsl::not_null<PeerData*> 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 <typename UpdateCallback>
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<Ui::RippleAnimation>(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<void()> updateCallback) {
void PeerListRow::createCheckbox(base::lambda<void()> updateCallback) {
_checkbox = std::make_unique<Ui::RoundImageCheckbox>(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<PeerListController*> 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> row) {
void PeerListBox::Inner::appendRow(std::unique_ptr<PeerListRow> 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> row) {
}
}
void PeerListBox::Inner::appendGlobalSearchRow(std::unique_ptr<Row> row) {
void PeerListBox::Inner::appendSearchRow(std::unique_ptr<PeerListRow> 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<PeerListRow*> row) {
Expects(showingSearch());
auto index = findRowIndex(row);
if (index.value < 0) {
_filterResults.push_back(row);
}
}
void PeerListBox::Inner::changeCheckState(gsl::not_null<PeerListRow*> 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<PeerListRow*> 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<PeerListRow*> 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<PeerListRow*> 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> row) {
void PeerListBox::Inner::prependRow(std::unique_ptr<PeerListRow> 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<PeerListRow*> 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<PeerListRow*> PeerListBox::Inner::rowAt(int index) const {
Expects(index >= 0 && index < _rows.size());
return _rows[index].get();
}
void PeerListBox::Inner::setAbout(object_ptr<Ui::FlatLabel> about) {
_about = std::move(about);
if (_about) {
_about->setParent(this);
void PeerListBox::Inner::setDescription(object_ptr<Ui::FlatLabel> description) {
_description = std::move(description);
if (_description) {
_description->setParent(this);
}
}
void PeerListBox::Inner::setSearchLoading(object_ptr<Ui::FlatLabel> loading) {
_searchLoading = std::move(loading);
if (_searchLoading) {
_searchLoading->setParent(this);
}
}
void PeerListBox::Inner::setSearchNoResults(object_ptr<Ui::FlatLabel> 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<Ui::FlatLabel>(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<Ui::FlatLabel> searchNoResults) {
_searchNoResults = std::move(searchNoResults);
if (_searchNoResults) {
_searchNoResults->setParent(this);
}
}
void PeerListBox::Inner::setSearchLoading(object_ptr<Ui::FlatLabel> 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<PeerListRow*> 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<Row*>*)nullptr;
clearSearchRows();
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
auto minimalList = (const std::vector<gsl::not_null<PeerListRow*>>*)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<SingleTimer>(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<PeerListRow*> 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<PeerListRow*> 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<PeerListRow*> 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<PeerListSearchController> 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&>(*row).history();
return history->inChatList(Dialogs::Mode::All);
});
// Place dialogs list before contactsNoDialogs list.
delegate()->peerListPartitionRows([](PeerListRow &a) {
auto history = static_cast<Row&>(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<PeerListBox::Row> ChatsListBoxController::createGlobalRow(PeerData *peer) {
std::unique_ptr<PeerListRow> ChatsListBoxController::createSearchRow(gsl::not_null<PeerData*> 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*>(row));
return false;
}
if (auto row = createRow(history)) {
view()->appendRow(std::move(row));
delegate()->peerListAppendRow(std::move(row));
return true;
}
return false;

View File

@ -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<PeerData*> peer);
PeerListRow(gsl::not_null<PeerData*> 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<PeerData*> 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<void()> 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 <typename UpdateCallback>
void setChecked(bool checked, SetStyle style, UpdateCallback callback) {
if (checked && !_checkbox) {
createCheckbox(std::move(callback));
}
setCheckedInternal(checked, style);
}
void invalidatePixmapsCache();
template <typename UpdateCallback>
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<QChar> &nameFirstChars) {
_nameFirstChars = nameFirstChars;
}
const OrderedSet<QChar> &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<void()> updateCallback);
void setCheckedInternal(bool checked, SetStyle style);
void paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const;
PeerListRowId _id = 0;
gsl::not_null<PeerData*> _peer;
bool _initialized = false;
std::unique_ptr<Ui::RippleAnimation> _ripple;
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
Text _name;
QString _status;
StatusType _statusType = StatusType::Online;
bool _disabled = false;
int _absoluteIndex = -1;
OrderedSet<QChar> _nameFirstChars;
bool _isSearchResult = false;
};
enum class PeerListSearchMode {
None,
Local,
Complex,
};
class PeerListDelegate {
public:
virtual void peerListSetTitle(base::lambda<QString()> title) = 0;
virtual void peerListSetDescription(object_ptr<Ui::FlatLabel> description) = 0;
virtual void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) = 0;
virtual void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) = 0;
virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0;
virtual void peerListAppendRow(std::unique_ptr<PeerListRow> row) = 0;
virtual void peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) = 0;
virtual void peerListAppendFoundRow(gsl::not_null<PeerListRow*> row) = 0;
virtual void peerListPrependRow(std::unique_ptr<PeerListRow> row) = 0;
virtual void peerListUpdateRow(gsl::not_null<PeerListRow*> row) = 0;
virtual bool peerListIsRowSelected(gsl::not_null<PeerData*> peer) = 0;
virtual void peerListRemoveRow(gsl::not_null<PeerListRow*> row) = 0;
virtual void peerListSetRowChecked(gsl::not_null<PeerListRow*> row, bool checked) = 0;
virtual gsl::not_null<PeerListRow*> 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<bool(PeerListRow &a, PeerListRow &b)> compare) = 0;
virtual void peerListPartitionRows(base::lambda<bool(PeerListRow &a)> border) = 0;
template <typename PeerDataRange>
void peerListAddSelectedRows(PeerDataRange &&range) {
for (auto peer : range) {
peerListAddSelectedRowInBunch(peer);
}
peerListFinishSelectedRowsBunch();
}
virtual std::vector<gsl::not_null<PeerData*>> peerListCollectSelectedRows() = 0;
virtual ~PeerListDelegate() = default;
private:
virtual void peerListAddSelectedRowInBunch(gsl::not_null<PeerData*> peer) = 0;
virtual void peerListFinishSelectedRowsBunch() = 0;
};
class PeerListSearchDelegate {
public:
virtual void peerListSearchAddRow(gsl::not_null<PeerData*> 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<PeerListSearchDelegate*> delegate) {
_delegate = delegate;
}
protected:
gsl::not_null<PeerListSearchDelegate*> delegate() const {
return _delegate;
}
private:
PeerListSearchDelegate *_delegate = nullptr;
};
class PeerListController : public PeerListSearchDelegate {
public:
// Search works only with RowId == peer->id.
PeerListController(std::unique_ptr<PeerListSearchController> searchController = nullptr);
void setDelegate(gsl::not_null<PeerListDelegate*> delegate) {
_delegate = delegate;
prepare();
}
virtual void prepare() = 0;
virtual void rowClicked(gsl::not_null<PeerListRow*> row) = 0;
virtual void rowActionClicked(gsl::not_null<PeerListRow*> row) {
}
virtual void preloadRows() {
}
bool isSearchLoading() const {
return _searchController ? _searchController->isLoading() : false;
}
virtual std::unique_ptr<PeerListRow> createSearchRow(gsl::not_null<PeerData*> peer) {
return std::unique_ptr<PeerListRow>();
}
bool isRowSelected(gsl::not_null<PeerData*> peer) {
return delegate()->peerListIsRowSelected(peer);
}
virtual bool searchInLocal() {
return true;
}
void search(const QString &query);
void peerListSearchAddRow(gsl::not_null<PeerData*> peer) override;
void peerListSearchRefreshRows() override;
virtual ~PeerListController() = default;
protected:
gsl::not_null<PeerListDelegate*> delegate() const {
return _delegate;
}
void setDescriptionText(const QString &text);
void setSearchLoadingText(const QString &text);
void setSearchNoResultsText(const QString &text);
void setDescription(object_ptr<Ui::FlatLabel> description) {
delegate()->peerListSetDescription(std::move(description));
}
void setSearchLoading(object_ptr<Ui::FlatLabel> loading) {
delegate()->peerListSetSearchLoading(std::move(loading));
}
void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults) {
delegate()->peerListSetSearchNoResults(std::move(noResults));
}
private:
PeerListDelegate *_delegate = nullptr;
std::unique_ptr<PeerListSearchController> _searchController = nullptr;
};
class PeerListBox : public BoxContent, public PeerListDelegate {
class Inner;
public:
using RowId = uint64;
PeerListBox(QWidget*, std::unique_ptr<PeerListController> controller, base::lambda<void(PeerListBox*)> 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<void()> 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 <typename UpdateCallback>
void setChecked(bool checked, SetStyle style, UpdateCallback callback) {
if (checked && !_checkbox) {
createCheckbox(std::move(callback));
}
setCheckedInternal(checked, style);
}
void invalidatePixmapsCache();
template <typename UpdateCallback>
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<QChar> &nameFirstChars) {
_nameFirstChars = nameFirstChars;
}
const OrderedSet<QChar> &nameFirstChars() const {
return _nameFirstChars;
}
private:
void createCheckbox(base::lambda<void()> 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<Ui::RippleAnimation> _ripple;
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
Text _name;
QString _status;
StatusType _statusType = StatusType::Online;
bool _disabled = false;
int _absoluteIndex = -1;
OrderedSet<QChar> _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<Row> createGlobalRow(PeerData *peer) {
return std::unique_ptr<Row>();
}
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> controller);
// Interface for the controller.
void appendRow(std::unique_ptr<Row> row);
void prependRow(std::unique_ptr<Row> 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<Ui::FlatLabel> 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<Ui::FlatLabel> searchNoResults);
void setSearchLoadingText(const QString &searchLoadingText);
void setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading);
template <typename PeerDataRange>
void addSelectedRows(PeerDataRange &&range) {
Expects(_select != nullptr);
for (auto peer : range) {
addSelectItem(peer, Row::SetStyle::Fast);
}
finishSelectItemsBunch();
void peerListSetTitle(base::lambda<QString()> title) override {
setTitle(std::move(title));
}
QVector<PeerData*> collectSelectedRows() const;
// callback takes two iterators, like [](auto &begin, auto &end).
template <typename ReorderCallback>
void reorderRows(ReorderCallback &&callback);
bool isRowSelected(PeerData *peer) const;
void peerListSetDescription(object_ptr<Ui::FlatLabel> description) override;
void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) override;
void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) override;
void peerListSetSearchMode(PeerListSearchMode mode) override;
void peerListAppendRow(std::unique_ptr<PeerListRow> row) override;
void peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) override;
void peerListAppendFoundRow(gsl::not_null<PeerListRow*> row) override;
void peerListPrependRow(std::unique_ptr<PeerListRow> row) override;
void peerListUpdateRow(gsl::not_null<PeerListRow*> row) override;
void peerListRemoveRow(gsl::not_null<PeerListRow*> row) override;
void peerListSetRowChecked(gsl::not_null<PeerListRow*> row, bool checked) override;
gsl::not_null<PeerListRow*> peerListRowAt(int index) override;
bool peerListIsRowSelected(gsl::not_null<PeerData*> peer) override;
std::vector<gsl::not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListRefreshRows() override;
void peerListScrollToTop() override;
int peerListFullRowsCount() override;
PeerListRow *peerListFindRow(PeerListRowId id) override;
void peerListSortRows(base::lambda<bool(PeerListRow &a, PeerListRow &b)> compare) override;
void peerListPartitionRows(base::lambda<bool(PeerListRow &a)> 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<PeerData*> peer) override {
addSelectItem(peer, PeerListRow::SetStyle::Fast);
}
void peerListFinishSelectedRowsBunch() override;
void addSelectItem(gsl::not_null<PeerData*> peer, PeerListRow::SetStyle style);
object_ptr<Ui::WidgetSlideWrap<Ui::MultiSelect>> createMultiSelect();
int getTopScrollSkip() const;
void updateScrollSkips();
@ -262,16 +349,17 @@ private:
QPointer<Inner> _inner;
std::unique_ptr<Controller> _controller;
std::unique_ptr<PeerListController> _controller;
base::lambda<void(PeerListBox*)> _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<PeerListController*> 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> row);
void prependRow(std::unique_ptr<Row> row);
Row *findRow(RowId id);
void updateRow(Row *row) {
void appendRow(std::unique_ptr<PeerListRow> row);
void appendSearchRow(std::unique_ptr<PeerListRow> row);
void appendFoundRow(gsl::not_null<PeerListRow*> row);
void prependRow(std::unique_ptr<PeerListRow> row);
PeerListRow *findRow(PeerListRowId id);
void updateRow(gsl::not_null<PeerListRow*> row) {
updateRow(row, RowIndex());
}
void removeRow(Row *row);
void removeRow(gsl::not_null<PeerListRow*> row);
int fullRowsCount() const;
Row *rowAt(int index) const;
void setAbout(object_ptr<Ui::FlatLabel> about);
gsl::not_null<PeerListRow*> rowAt(int index) const;
void setDescription(object_ptr<Ui::FlatLabel> description);
void setSearchLoading(object_ptr<Ui::FlatLabel> loading);
void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults);
void refreshRows();
void setSearchMode(SearchMode mode);
void setSearchNoResults(object_ptr<Ui::FlatLabel> searchNoResults);
void setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading);
void changeCheckState(Row *row, bool checked, Row::SetStyle style);
void setSearchMode(PeerListSearchMode mode);
void changeCheckState(gsl::not_null<PeerListRow*> row, bool checked, PeerListRow::SetStyle style);
template <typename ReorderCallback>
void reorderRows(ReorderCallback &&callback) {
@ -327,7 +417,6 @@ protected:
private:
void refreshIndices();
void appendGlobalSearchRow(std::unique_ptr<Row> row);
void invalidatePixmapsCache();
@ -370,19 +459,19 @@ private:
void loadProfilePhotos();
void checkScrollForPreload();
void updateRow(Row *row, RowIndex hint);
void updateRow(gsl::not_null<PeerListRow*> 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<PeerListRow*> row, RowIndex hint = RowIndex());
QRect getActionRect(gsl::not_null<PeerListRow*> 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<PeerListRow*> row);
void addToSearchIndex(gsl::not_null<PeerListRow*> row);
bool addingToSearchIndex() const;
void removeFromSearchIndex(Row *row);
void removeFromSearchIndex(gsl::not_null<PeerListRow*> 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<PeerListController*> _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<std::unique_ptr<Row>> _rows;
std::map<RowId, Row*> _rowsById;
std::map<PeerData*, std::vector<Row*>> _rowsByPeer;
std::vector<std::unique_ptr<PeerListRow>> _rows;
std::map<PeerListRowId, gsl::not_null<PeerListRow*>> _rowsById;
std::map<PeerData*, std::vector<gsl::not_null<PeerListRow*>>> _rowsByPeer;
SearchMode _searchMode = SearchMode::None;
std::map<QChar, std::vector<Row*>> _searchIndex;
std::map<QChar, std::vector<gsl::not_null<PeerListRow*>>> _searchIndex;
QString _searchQuery;
std::vector<Row*> _filterResults;
std::vector<gsl::not_null<PeerListRow*>> _filterResults;
object_ptr<Ui::FlatLabel> _about = { nullptr };
object_ptr<Ui::FlatLabel> _description = { nullptr };
object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
QPoint _lastMousePosition;
std::vector<std::unique_ptr<Row>> _globalSearchRows;
object_ptr<SingleTimer> _globalSearchTimer = { nullptr };
QString _globalSearchQuery;
QString _globalSearchHighlight;
mtpRequestId _globalSearchRequestId = 0;
std::map<QString, MTPcontacts_Found> _globalSearchCache;
std::map<mtpRequestId, QString> _globalSearchQueries;
std::vector<std::unique_ptr<PeerListRow>> _searchRows;
};
template <typename ReorderCallback>
inline void PeerListBox::reorderRows(ReorderCallback &&callback) {
_inner->reorderRows(std::forward<ReorderCallback>(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<QString, MTPcontacts_Found> _cache;
std::map<mtpRequestId, QString> _queries;
};
class ChatsListBoxController : public PeerListController, protected base::Subscriber {
public:
ChatsListBoxController(std::unique_ptr<PeerListSearchController> searchController = std::make_unique<PeerListGlobalSearchController>());
void prepare() override final;
std::unique_ptr<PeerListBox::Row> createGlobalRow(PeerData *peer) override final;
std::unique_ptr<PeerListRow> createSearchRow(gsl::not_null<PeerData*> 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*> history) : PeerListRow(history->peer), _history(history) {
}
History *history() const {
gsl::not_null<History*> history() const {
return _history;
}
private:
History *_history = nullptr;
gsl::not_null<History*> _history;
};
virtual std::unique_ptr<Row> createRow(History *history) = 0;
virtual std::unique_ptr<Row> createRow(gsl::not_null<History*> history) = 0;
virtual void prepareViewHook() = 0;
virtual void updateRowHook(Row *row) {
}

View File

@ -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<void()> 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*>(row);
void BoxController::rowClicked(gsl::not_null<PeerListRow*> row) {
auto itemsRow = static_cast<Row*>(row.get());
auto itemId = itemsRow->maxItemId();
Ui::showPeerHistoryAsync(row->peer()->id, itemId);
}
void BoxController::rowActionClicked(PeerListBox::Row *row) {
void BoxController::rowActionClicked(gsl::not_null<PeerListRow*> row) {
auto user = row->peer()->asUser();
t_assert(user != nullptr);
@ -274,7 +272,7 @@ void BoxController::receivedCalls(const QVector<MTPMessage> &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<Row&>(*a).maxItemId() > static_cast<Row&>(*a).maxItemId();
});
(way == InsertWay::Append) ? delegate()->peerListAppendRow(createRow(item)) : delegate()->peerListPrependRow(createRow(item));
delegate()->peerListSortRows([](PeerListRow &a, PeerListRow &b) {
return static_cast<Row&>(a).maxItemId() > static_cast<Row&>(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<Row*>(v->rowAt(fullRowsCount - 1));
auto lastRow = static_cast<Row*>(v->peerListRowAt(fullRowsCount - 1).get());
if (itemId < lastRow->minItemId()) {
return lastRow;
}
auto firstRow = static_cast<Row*>(v->rowAt(0));
auto firstRow = static_cast<Row*>(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<Row*>(v->rowAt(middle));
auto middleRow = static_cast<Row*>(v->peerListRowAt(middle).get());
if (middleRow->maxItemId() >= itemId) {
left = middle;
} else {
right = middle;
}
}
auto result = static_cast<Row*>(v->rowAt(left));
auto result = static_cast<Row*>(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<Row*>(v->rowAt(left + 1));
auto possibleResult = static_cast<Row*>(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<PeerListBox::Row> BoxController::createRow(HistoryItem *item) const {
std::unique_ptr<PeerListRow> BoxController::createRow(HistoryItem *item) const {
auto row = std::make_unique<Row>(item);
return std::move(row);
}

View File

@ -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<PeerListRow*> row) override;
void rowActionClicked(gsl::not_null<PeerListRow*> row) override;
void preloadRows() override;
private:
@ -43,7 +43,7 @@ private:
Prepend,
};
bool insertRow(HistoryItem *item, InsertWay way);
std::unique_ptr<PeerListBox::Row> createRow(HistoryItem *item) const;
std::unique_ptr<PeerListRow> createRow(HistoryItem *item) const;
MsgId _offsetId = 0;
mtpRequestId _loadRequestId = 0;

View File

@ -90,13 +90,13 @@ void GroupMembersWidget::restrictUser(gsl::not_null<UserData*> user) {
}
auto defaultRestricted = MegagroupInfo::Restricted { EditRestrictedBox::DefaultRights(megagroup) };
auto currentRights = megagroup->mgInfo->lastRestricted.value(user, defaultRestricted).rights;
Ui::show(Box<EditRestrictedBox>(megagroup, user, currentRights, base::lambda_guarded(this, [this, megagroup, user](const MTPChannelBannedRights &rights) {
Ui::show(Box<EditRestrictedBox>(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) {

View File

@ -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<ChannelData*> channel, bool restricted, gsl::not_null<std::map<UserData*, MTPChannelBannedRights>*> 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<ChannelData*> _channel;
bool _restricted = false;
gsl::not_null<std::map<UserData*, MTPChannelBannedRights>*> _rights;
base::Timer _timer;
QString _query;
mtpRequestId _requestId = 0;
int _offset = 0;
bool _allLoaded = false;
std::map<QString, MTPchannels_ChannelParticipants> _cache;
std::map<mtpRequestId, QString> _queries;
};
BlockedBoxSearchController::BlockedBoxSearchController(gsl::not_null<ChannelData*> channel, bool restricted, gsl::not_null<std::map<UserData*, MTPChannelBannedRights>*> 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<ChannelData*> channel, bool restricted);
void prepare() override;
void rowClicked(PeerListBox::Row *row) override;
void rowActionClicked(PeerListBox::Row *row) override;
void rowClicked(gsl::not_null<PeerListRow*> row) override;
void rowActionClicked(gsl::not_null<PeerListRow*> row) override;
void preloadRows() override;
bool searchInLocal() override {
return false;
}
void peerListSearchAddRow(gsl::not_null<PeerData*> peer) override;
private:
bool appendRow(UserData *user);
bool prependRow(UserData *user);
std::unique_ptr<PeerListBox::Row> createRow(UserData *user) const;
std::unique_ptr<PeerListRow> createRow(UserData *user) const;
ChannelData *_channel = nullptr;
gsl::not_null<ChannelData*> _channel;
bool _restricted = false;
int _offset = 0;
mtpRequestId _loadRequestId = 0;
bool _allLoaded = false;
std::map<UserData*, MTPChannelBannedRights> _rights;
QPointer<EditRestrictedBox> _editBox;
};
void BlockedBoxController::peerListSearchAddRow(gsl::not_null<PeerData*> peer) {
PeerListController::peerListSearchAddRow(peer);
if (_restricted && delegate()->peerListFullRowsCount() > 0) {
setDescriptionText(QString());
}
}
BlockedBoxController::BlockedBoxController(gsl::not_null<ChannelData*> channel, bool restricted) : PeerListController(std::make_unique<BlockedBoxSearchController>(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<PeerListRow*> row) {
Ui::showPeerHistoryAsync(row->peer()->id, ShowAtUnreadMsgId);
}
void BlockedBoxController::rowActionClicked(PeerListBox::Row *row) {
void BlockedBoxController::rowActionClicked(gsl::not_null<PeerListRow*> 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<BlockedBoxController>(this);
_editBox = Ui::show(Box<EditRestrictedBox>(_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<PeerListBox::Row> BlockedBoxController::createRow(UserData *user) const {
std::unique_ptr<PeerListRow> BlockedBoxController::createRow(UserData *user) const {
auto row = std::make_unique<PeerListRowWithLink>(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<PeerListBox>(std::make_unique<BlockedBoxController>(channel)));
Ui::show(Box<PeerListBox>(std::make_unique<BlockedBoxController>(channel, false), [](PeerListBox *box) {
box->addButton(langFactory(lng_close), [box] { box->closeBox(); });
}));
}
}
void SettingsWidget::onManageRestrictedUsers() {
if (auto channel = peer()->asMegagroup()) {
Ui::show(Box<PeerListBox>(std::make_unique<BlockedBoxController>(channel, true), [](PeerListBox *box) {
box->addButton(langFactory(lng_close), [box] { box->closeBox(); });
}));
}
}

View File

@ -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<Ui::LeftOutlineButton> _manageAdmins = { nullptr };
object_ptr<Ui::LeftOutlineButton> _manageBlockedUsers = { nullptr };
object_ptr<Ui::LeftOutlineButton> _manageRestrictedUsers = { nullptr };
object_ptr<Ui::LeftOutlineButton> _inviteLink = { nullptr };
};

View File

@ -35,36 +35,40 @@ constexpr auto kBlockedPerPage = 40;
class BlockUserBoxController : public ChatsListBoxController {
public:
void rowClicked(PeerListBox::Row *row) override;
void rowClicked(gsl::not_null<PeerListRow*> row) override;
void setBlockUserCallback(base::lambda<void(gsl::not_null<UserData*> user)> callback) {
_blockUserCallback = std::move(callback);
}
protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(History *history) override;
std::unique_ptr<Row> createRow(gsl::not_null<History*> 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<PeerListRow*> row, UserData *user) const;
base::lambda<void(gsl::not_null<UserData*> 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<PeerListRow*> 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<PeerListRow*> row) {
_blockUserCallback(row->peer()->asUser());
}
std::unique_ptr<BlockUserBoxController::Row> BlockUserBoxController::createRow(History *history) {
std::unique_ptr<BlockUserBoxController::Row> BlockUserBoxController::createRow(gsl::not_null<History*> history) {
if (auto user = history->peer->asUser()) {
auto row = std::make_unique<Row>(history);
updateIsBlocked(row.get(), user);
@ -94,11 +94,9 @@ std::unique_ptr<BlockUserBoxController::Row> 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<PeerListRow*> row) {
Ui::showPeerHistoryAsync(row->peer()->id, ShowAtUnreadMsgId);
}
void BlockedBoxController::rowActionClicked(PeerListBox::Row *row) {
void BlockedBoxController::rowActionClicked(gsl::not_null<PeerListRow*> row) {
auto user = row->peer()->asUser();
Expects(user != nullptr);
@ -168,42 +166,50 @@ void BlockedBoxController::receivedUsers(const QVector<MTPContactBlocked> &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<PeerListBox>(std::make_unique<BlockUserBoxController>()), KeepOtherLayers);
void BlockedBoxController::BlockNewUser() {
auto controller = std::make_unique<BlockUserBoxController>();
auto initBox = [controller = controller.get()](PeerListBox *box) {
controller->setBlockUserCallback([box](gsl::not_null<UserData*> user) {
App::api()->blockUser(user);
box->closeBox();
});
box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); });
};
Ui::show(Box<PeerListBox>(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<PeerListBox::Row> BlockedBoxController::createRow(UserData *user) const {
std::unique_ptr<PeerListRow> BlockedBoxController::createRow(UserData *user) const {
auto row = std::make_unique<PeerListRowWithLink>(user);
row->setActionLink(lang(lng_blocked_list_unblock));
auto status = [user]() -> QString {

View File

@ -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<PeerListRow*> row) override;
void rowActionClicked(gsl::not_null<PeerListRow*> row) override;
void preloadRows() override;
static void BlockNewUser();
private:
void receivedUsers(const QVector<MTPContactBlocked> &result);
void handleBlockedEvent(UserData *user);
void blockUser();
bool appendRow(UserData *user);
bool prependRow(UserData *user);
std::unique_ptr<PeerListBox::Row> createRow(UserData *user) const;
std::unique_ptr<PeerListRow> createRow(UserData *user) const;
int _offset = 0;
mtpRequestId _loadRequestId = 0;

View File

@ -208,7 +208,10 @@ void PrivacyWidget::autoLockUpdated() {
}
void PrivacyWidget::onBlockedUsers() {
Ui::show(Box<PeerListBox>(std::make_unique<BlockedBoxController>()));
Ui::show(Box<PeerListBox>(std::make_unique<BlockedBoxController>(), [](PeerListBox *box) {
box->addButton(langFactory(lng_close), [box] { box->closeBox(); });
box->addLeftButton(langFactory(lng_blocked_list_add), [box] { BlockedBoxController::BlockNewUser(); });
}));
}
void PrivacyWidget::onLastSeenPrivacy() {

View File

@ -86,7 +86,9 @@ void MainMenu::refreshMenu() {
}, &st::mainMenuContacts, &st::mainMenuContactsOver);
if (Global::PhoneCallsEnabled()) {
_menu->addAction(lang(lng_menu_calls), [] {
Ui::show(Box<PeerListBox>(std::make_unique<Calls::BoxController>()));
Ui::show(Box<PeerListBox>(std::make_unique<Calls::BoxController>(), [](PeerListBox *box) {
box->addButton(langFactory(lng_close), [box] { box->closeBox(); });
}));
}, &st::mainMenuCalls, &st::mainMenuCallsOver);
}
_menu->addAction(lang(lng_menu_settings), [] {