mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-04 21:37:45 +00:00
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:
parent
5c0a1bafe2
commit
7fdac9cd94
@ -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";
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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 &¤t) {
|
||||
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;
|
||||
|
@ -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) {
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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(); });
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 };
|
||||
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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), [] {
|
||||
|
Loading…
Reference in New Issue
Block a user