2017-08-14 12:53:49 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2017-08-14 12:53:49 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2017-08-14 12:53:49 +00:00
|
|
|
*/
|
|
|
|
#include "boxes/peer_list_controllers.h"
|
|
|
|
|
2021-11-24 04:25:05 +00:00
|
|
|
#include "api/api_chat_participants.h"
|
2021-09-15 10:21:45 +00:00
|
|
|
#include "base/random.h"
|
2021-10-18 21:36:55 +00:00
|
|
|
#include "ui/boxes/confirm_box.h"
|
2017-08-14 12:48:11 +00:00
|
|
|
#include "ui/widgets/checkbox.h"
|
2022-09-16 20:23:27 +00:00
|
|
|
#include "ui/painter.h"
|
2019-09-13 12:22:54 +00:00
|
|
|
#include "ui/ui_utility.h"
|
2019-07-24 11:45:24 +00:00
|
|
|
#include "main/main_session.h"
|
2018-01-04 10:22:53 +00:00
|
|
|
#include "data/data_session.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "data/data_channel.h"
|
|
|
|
#include "data/data_chat.h"
|
|
|
|
#include "data/data_user.h"
|
2022-11-01 04:46:31 +00:00
|
|
|
#include "data/data_forum.h"
|
|
|
|
#include "data/data_forum_topic.h"
|
2019-04-22 18:18:57 +00:00
|
|
|
#include "data/data_folder.h"
|
2020-02-21 10:29:48 +00:00
|
|
|
#include "data/data_histories.h"
|
2021-12-22 15:14:16 +00:00
|
|
|
#include "data/data_changes.h"
|
2022-11-01 04:46:31 +00:00
|
|
|
#include "dialogs/ui/dialogs_layout.h"
|
2017-08-14 12:48:11 +00:00
|
|
|
#include "apiwrap.h"
|
2017-08-14 12:53:49 +00:00
|
|
|
#include "mainwidget.h"
|
2020-10-07 17:08:50 +00:00
|
|
|
#include "mainwindow.h"
|
2017-08-14 12:53:49 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2018-01-13 12:45:11 +00:00
|
|
|
#include "history/history.h"
|
2022-11-01 04:46:31 +00:00
|
|
|
#include "history/history_item.h"
|
2019-04-22 14:22:39 +00:00
|
|
|
#include "dialogs/dialogs_main_list.h"
|
2021-01-22 12:16:18 +00:00
|
|
|
#include "window/window_session_controller.h" // showAddContact()
|
2021-12-22 15:14:16 +00:00
|
|
|
#include "base/unixtime.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "styles/style_boxes.h"
|
|
|
|
#include "styles/style_profile.h"
|
2022-11-01 04:46:31 +00:00
|
|
|
#include "styles/style_dialogs.h"
|
2017-08-14 12:53:49 +00:00
|
|
|
|
2017-08-14 12:48:11 +00:00
|
|
|
namespace {
|
|
|
|
|
2021-12-22 15:14:16 +00:00
|
|
|
constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000);
|
2022-11-01 04:46:31 +00:00
|
|
|
constexpr auto kSearchPerPage = 50;
|
2021-12-22 15:14:16 +00:00
|
|
|
|
2017-08-14 12:48:11 +00:00
|
|
|
} // namespace
|
|
|
|
|
2020-10-07 17:08:50 +00:00
|
|
|
object_ptr<Ui::BoxContent> PrepareContactsBox(
|
|
|
|
not_null<Window::SessionController*> sessionController) {
|
2021-12-22 15:14:16 +00:00
|
|
|
using Mode = ContactsBoxController::SortMode;
|
|
|
|
auto controller = std::make_unique<ContactsBoxController>(
|
|
|
|
&sessionController->session());
|
|
|
|
const auto raw = controller.get();
|
|
|
|
auto init = [=](not_null<PeerListBox*> box) {
|
|
|
|
struct State {
|
|
|
|
QPointer<Ui::IconButton> toggleSort;
|
|
|
|
Mode mode = ContactsBoxController::SortMode::Online;
|
|
|
|
};
|
|
|
|
const auto state = box->lifetime().make_state<State>();
|
2020-10-07 17:08:50 +00:00
|
|
|
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
|
|
|
box->addLeftButton(
|
|
|
|
tr::lng_profile_add_contact(),
|
2021-12-22 15:14:16 +00:00
|
|
|
[=] { sessionController->showAddContact(); });
|
|
|
|
state->toggleSort = box->addTopButton(st::contactsSortButton, [=] {
|
|
|
|
const auto online = (state->mode == Mode::Online);
|
|
|
|
state->mode = online ? Mode::Alphabet : Mode::Online;
|
|
|
|
raw->setSortMode(state->mode);
|
|
|
|
state->toggleSort->setIconOverride(
|
|
|
|
online ? &st::contactsSortOnlineIcon : nullptr,
|
|
|
|
online ? &st::contactsSortOnlineIconOver : nullptr);
|
|
|
|
});
|
|
|
|
raw->setSortMode(Mode::Online);
|
2020-10-07 17:08:50 +00:00
|
|
|
};
|
2021-12-22 15:14:16 +00:00
|
|
|
return Box<PeerListBox>(std::move(controller), std::move(init));
|
2020-10-07 17:08:50 +00:00
|
|
|
}
|
|
|
|
|
2017-08-14 12:53:49 +00:00
|
|
|
void PeerListRowWithLink::setActionLink(const QString &action) {
|
|
|
|
_action = action;
|
|
|
|
refreshActionLink();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListRowWithLink::refreshActionLink() {
|
|
|
|
if (!isInitialized()) return;
|
|
|
|
_actionWidth = _action.isEmpty() ? 0 : st::normalFont->width(_action);
|
|
|
|
}
|
|
|
|
|
2017-10-10 20:39:44 +00:00
|
|
|
void PeerListRowWithLink::lazyInitialize(const style::PeerListItem &st) {
|
|
|
|
PeerListRow::lazyInitialize(st);
|
2017-08-14 12:53:49 +00:00
|
|
|
refreshActionLink();
|
|
|
|
}
|
|
|
|
|
2021-10-13 12:37:38 +00:00
|
|
|
QSize PeerListRowWithLink::rightActionSize() const {
|
2017-08-14 12:53:49 +00:00
|
|
|
return QSize(_actionWidth, st::normalFont->height);
|
|
|
|
}
|
|
|
|
|
2021-10-13 12:37:38 +00:00
|
|
|
QMargins PeerListRowWithLink::rightActionMargins() const {
|
2017-11-07 17:53:00 +00:00
|
|
|
return QMargins(
|
|
|
|
st::contactsCheckPosition.x(),
|
|
|
|
(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom() - st::normalFont->height) / 2,
|
|
|
|
st::defaultPeerListItem.photoPosition.x() + st::contactsCheckPosition.x(),
|
|
|
|
0);
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
2021-10-13 12:37:38 +00:00
|
|
|
void PeerListRowWithLink::rightActionPaint(
|
2017-11-07 17:53:00 +00:00
|
|
|
Painter &p,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int outerWidth,
|
|
|
|
bool selected,
|
|
|
|
bool actionSelected) {
|
2017-08-14 12:53:49 +00:00
|
|
|
p.setFont(actionSelected ? st::linkOverFont : st::linkFont);
|
|
|
|
p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color);
|
|
|
|
p.drawTextLeft(x, y, outerWidth, _action, _actionWidth);
|
|
|
|
}
|
|
|
|
|
2019-07-25 18:55:11 +00:00
|
|
|
PeerListGlobalSearchController::PeerListGlobalSearchController(
|
2020-12-21 19:43:23 +00:00
|
|
|
not_null<Main::Session*> session)
|
|
|
|
: _session(session)
|
|
|
|
, _api(&session->mtp()) {
|
2017-08-14 12:53:49 +00:00
|
|
|
_timer.setCallback([this] { searchOnServer(); });
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListGlobalSearchController::searchQuery(const QString &query) {
|
|
|
|
if (_query != query) {
|
|
|
|
_query = query;
|
|
|
|
_requestId = 0;
|
2018-01-22 10:58:11 +00:00
|
|
|
if (!_query.isEmpty() && !searchInCache()) {
|
2017-08-14 12:53:49 +00:00
|
|
|
_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() {
|
2019-11-27 08:02:56 +00:00
|
|
|
_requestId = _api.request(MTPcontacts_Search(
|
2018-01-22 10:58:11 +00:00
|
|
|
MTP_string(_query),
|
|
|
|
MTP_int(SearchPeopleLimit)
|
|
|
|
)).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) {
|
2017-08-14 12:53:49 +00:00
|
|
|
searchDone(result, requestId);
|
2021-03-12 12:48:00 +00:00
|
|
|
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
2017-08-14 12:53:49 +00:00
|
|
|
if (_requestId == requestId) {
|
|
|
|
_requestId = 0;
|
|
|
|
delegate()->peerListSearchRefreshRows();
|
|
|
|
}
|
|
|
|
}).send();
|
|
|
|
_queries.emplace(_requestId, _query);
|
|
|
|
}
|
|
|
|
|
2018-01-22 10:58:11 +00:00
|
|
|
void PeerListGlobalSearchController::searchDone(
|
|
|
|
const MTPcontacts_Found &result,
|
|
|
|
mtpRequestId requestId) {
|
2017-08-14 12:53:49 +00:00
|
|
|
Expects(result.type() == mtpc_contacts_found);
|
|
|
|
|
|
|
|
auto &contacts = result.c_contacts_found();
|
|
|
|
auto query = _query;
|
|
|
|
if (requestId) {
|
2020-12-21 19:43:23 +00:00
|
|
|
_session->data().processUsers(contacts.vusers());
|
|
|
|
_session->data().processChats(contacts.vchats());
|
2017-08-14 12:53:49 +00:00
|
|
|
auto it = _queries.find(requestId);
|
|
|
|
if (it != _queries.cend()) {
|
|
|
|
query = it->second;
|
|
|
|
_cache[query] = result;
|
|
|
|
_queries.erase(it);
|
|
|
|
}
|
|
|
|
}
|
2018-01-22 10:58:11 +00:00
|
|
|
const auto feedList = [&](const MTPVector<MTPPeer> &list) {
|
|
|
|
for (const auto &mtpPeer : list.v) {
|
2020-12-21 19:43:23 +00:00
|
|
|
const auto peer = _session->data().peerLoaded(
|
2019-07-25 18:55:11 +00:00
|
|
|
peerFromMTP(mtpPeer));
|
|
|
|
if (peer) {
|
2017-08-14 12:53:49 +00:00
|
|
|
delegate()->peerListSearchAddRow(peer);
|
|
|
|
}
|
|
|
|
}
|
2018-01-22 10:58:11 +00:00
|
|
|
};
|
|
|
|
if (_requestId == requestId) {
|
|
|
|
_requestId = 0;
|
2019-07-05 13:38:38 +00:00
|
|
|
feedList(contacts.vmy_results());
|
|
|
|
feedList(contacts.vresults());
|
2017-08-14 12:53:49 +00:00
|
|
|
delegate()->peerListSearchRefreshRows();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PeerListGlobalSearchController::isLoading() {
|
|
|
|
return _timer.isActive() || _requestId;
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
ChatsListBoxController::Row::Row(not_null<History*> history)
|
|
|
|
: PeerListRow(history->peer)
|
|
|
|
, _history(history) {
|
|
|
|
}
|
|
|
|
|
2019-07-25 18:55:11 +00:00
|
|
|
ChatsListBoxController::ChatsListBoxController(
|
2020-12-21 19:43:23 +00:00
|
|
|
not_null<Main::Session*> session)
|
2019-07-25 18:55:11 +00:00
|
|
|
: ChatsListBoxController(
|
2020-12-21 19:43:23 +00:00
|
|
|
std::make_unique<PeerListGlobalSearchController>(session)) {
|
2019-07-25 18:55:11 +00:00
|
|
|
}
|
|
|
|
|
2017-12-05 14:07:01 +00:00
|
|
|
ChatsListBoxController::ChatsListBoxController(
|
|
|
|
std::unique_ptr<PeerListSearchController> searchController)
|
|
|
|
: PeerListController(std::move(searchController)) {
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatsListBoxController::prepare() {
|
2019-06-19 15:09:03 +00:00
|
|
|
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
|
2017-08-14 12:53:49 +00:00
|
|
|
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
|
|
|
|
|
|
|
prepareViewHook();
|
|
|
|
|
2019-07-25 18:55:11 +00:00
|
|
|
if (!session().data().chatsListLoaded()) {
|
|
|
|
session().data().chatsListLoadedEvents(
|
2019-04-18 08:28:43 +00:00
|
|
|
) | rpl::filter([=](Data::Folder *folder) {
|
|
|
|
return !folder;
|
|
|
|
}) | rpl::start_with_next([=] {
|
|
|
|
checkForEmptyRows();
|
|
|
|
}, lifetime());
|
|
|
|
}
|
2017-08-14 12:53:49 +00:00
|
|
|
|
2019-07-25 18:55:11 +00:00
|
|
|
session().data().chatsListChanges(
|
2019-04-22 18:18:57 +00:00
|
|
|
) | rpl::start_with_next([=] {
|
2017-08-14 12:53:49 +00:00
|
|
|
rebuildRows();
|
2019-04-18 08:28:43 +00:00
|
|
|
}, lifetime());
|
|
|
|
|
2019-07-25 18:55:11 +00:00
|
|
|
session().data().contactsLoaded().value(
|
2019-04-18 08:28:43 +00:00
|
|
|
) | rpl::start_with_next([=] {
|
2017-08-14 12:53:49 +00:00
|
|
|
rebuildRows();
|
2019-04-18 08:28:43 +00:00
|
|
|
}, lifetime());
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatsListBoxController::rebuildRows() {
|
|
|
|
auto wasEmpty = !delegate()->peerListFullRowsCount();
|
|
|
|
auto appendList = [this](auto chats) {
|
|
|
|
auto count = 0;
|
2021-09-08 10:53:54 +00:00
|
|
|
for (const auto &row : chats->all()) {
|
2018-01-22 09:33:09 +00:00
|
|
|
if (const auto history = row->history()) {
|
|
|
|
if (appendRow(history)) {
|
|
|
|
++count;
|
|
|
|
}
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
};
|
2017-12-05 14:07:01 +00:00
|
|
|
auto added = 0;
|
|
|
|
if (respectSavedMessagesChat()) {
|
2019-07-25 18:55:11 +00:00
|
|
|
if (appendRow(session().data().history(session().user()))) {
|
2018-09-11 12:50:40 +00:00
|
|
|
++added;
|
2017-12-05 14:07:01 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-25 18:55:11 +00:00
|
|
|
added += appendList(session().data().chatsList()->indexed());
|
2019-04-22 18:18:57 +00:00
|
|
|
const auto id = Data::Folder::kId;
|
2019-07-25 18:55:11 +00:00
|
|
|
if (const auto folder = session().data().folderLoaded(id)) {
|
2019-04-22 18:18:57 +00:00
|
|
|
added += appendList(folder->chatsList()->indexed());
|
|
|
|
}
|
2019-07-25 18:55:11 +00:00
|
|
|
added += appendList(session().data().contactsNoChatsList());
|
2017-08-14 12:53:49 +00:00
|
|
|
if (!wasEmpty && added > 0) {
|
|
|
|
// Place dialogs list before contactsNoDialogs list.
|
2017-10-22 17:06:57 +00:00
|
|
|
delegate()->peerListPartitionRows([](const PeerListRow &a) {
|
2019-04-17 13:22:37 +00:00
|
|
|
const auto history = static_cast<const Row&>(a).history();
|
2019-04-22 14:22:39 +00:00
|
|
|
return history->inChatList();
|
2017-08-14 12:53:49 +00:00
|
|
|
});
|
2017-12-05 14:07:01 +00:00
|
|
|
if (respectSavedMessagesChat()) {
|
|
|
|
delegate()->peerListPartitionRows([](const PeerListRow &a) {
|
|
|
|
return a.peer()->isSelf();
|
|
|
|
});
|
|
|
|
}
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
checkForEmptyRows();
|
|
|
|
delegate()->peerListRefreshRows();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatsListBoxController::checkForEmptyRows() {
|
|
|
|
if (delegate()->peerListFullRowsCount()) {
|
|
|
|
setDescriptionText(QString());
|
|
|
|
} else {
|
2019-07-25 18:55:11 +00:00
|
|
|
const auto loaded = session().data().contactsLoaded().current()
|
|
|
|
&& session().data().chatsListLoaded();
|
2019-06-19 15:09:03 +00:00
|
|
|
setDescriptionText(loaded ? emptyBoxText() : tr::lng_contacts_loading(tr::now));
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-15 09:35:19 +00:00
|
|
|
QString ChatsListBoxController::emptyBoxText() const {
|
2019-06-19 15:09:03 +00:00
|
|
|
return tr::lng_contacts_not_found(tr::now);
|
2017-08-15 09:35:19 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 04:46:31 +00:00
|
|
|
std::unique_ptr<PeerListRow> ChatsListBoxController::createSearchRow(
|
|
|
|
not_null<PeerData*> peer) {
|
2019-01-18 12:27:37 +00:00
|
|
|
return createRow(peer->owner().history(peer));
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
2017-08-17 08:31:24 +00:00
|
|
|
bool ChatsListBoxController::appendRow(not_null<History*> history) {
|
2021-04-01 21:04:10 +00:00
|
|
|
if (auto row = delegate()->peerListFindRow(history->peer->id.value)) {
|
2017-08-14 12:53:49 +00:00
|
|
|
updateRowHook(static_cast<Row*>(row));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (auto row = createRow(history)) {
|
|
|
|
delegate()->peerListAppendRow(std::move(row));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-13 13:28:05 +00:00
|
|
|
ContactsBoxController::ContactsBoxController(
|
2020-12-21 19:43:23 +00:00
|
|
|
not_null<Main::Session*> session)
|
|
|
|
: ContactsBoxController(
|
|
|
|
session,
|
|
|
|
std::make_unique<PeerListGlobalSearchController>(session)) {
|
2019-07-25 18:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ContactsBoxController::ContactsBoxController(
|
2020-12-21 19:43:23 +00:00
|
|
|
not_null<Main::Session*> session,
|
2019-01-13 13:28:05 +00:00
|
|
|
std::unique_ptr<PeerListSearchController> searchController)
|
2019-07-25 18:55:11 +00:00
|
|
|
: PeerListController(std::move(searchController))
|
2021-12-22 15:14:16 +00:00
|
|
|
, _session(session)
|
|
|
|
, _sortByOnlineTimer([=] { sort(); }) {
|
2019-07-25 18:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Main::Session &ContactsBoxController::session() const {
|
2020-12-21 19:43:23 +00:00
|
|
|
return *_session;
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ContactsBoxController::prepare() {
|
2019-06-19 15:09:03 +00:00
|
|
|
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
|
2017-08-14 12:53:49 +00:00
|
|
|
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
2019-06-18 15:00:55 +00:00
|
|
|
delegate()->peerListSetTitle(tr::lng_contacts_header());
|
2017-08-14 12:53:49 +00:00
|
|
|
|
|
|
|
prepareViewHook();
|
|
|
|
|
2019-07-25 18:55:11 +00:00
|
|
|
session().data().contactsLoaded().value(
|
2019-04-18 08:28:43 +00:00
|
|
|
) | rpl::start_with_next([=] {
|
2017-08-14 12:53:49 +00:00
|
|
|
rebuildRows();
|
2019-04-18 08:28:43 +00:00
|
|
|
}, lifetime());
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ContactsBoxController::rebuildRows() {
|
2019-04-16 14:05:56 +00:00
|
|
|
const auto appendList = [&](auto chats) {
|
2017-08-14 12:53:49 +00:00
|
|
|
auto count = 0;
|
2021-09-08 10:53:54 +00:00
|
|
|
for (const auto &row : chats->all()) {
|
2018-01-22 09:33:09 +00:00
|
|
|
if (const auto history = row->history()) {
|
|
|
|
if (const auto user = history->peer->asUser()) {
|
|
|
|
if (appendRow(user)) {
|
|
|
|
++count;
|
|
|
|
}
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
};
|
2019-07-25 18:55:11 +00:00
|
|
|
appendList(session().data().contactsList());
|
2017-08-14 12:53:49 +00:00
|
|
|
checkForEmptyRows();
|
2021-12-22 15:14:16 +00:00
|
|
|
sort();
|
2017-08-14 12:53:49 +00:00
|
|
|
delegate()->peerListRefreshRows();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ContactsBoxController::checkForEmptyRows() {
|
2019-06-19 15:09:03 +00:00
|
|
|
setDescriptionText(delegate()->peerListFullRowsCount()
|
|
|
|
? QString()
|
2019-07-25 18:55:11 +00:00
|
|
|
: session().data().contactsLoaded().current()
|
2019-06-19 15:09:03 +00:00
|
|
|
? tr::lng_contacts_not_found(tr::now)
|
|
|
|
: tr::lng_contacts_loading(tr::now));
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
2018-02-03 19:52:35 +00:00
|
|
|
std::unique_ptr<PeerListRow> ContactsBoxController::createSearchRow(
|
|
|
|
not_null<PeerData*> peer) {
|
|
|
|
if (const auto user = peer->asUser()) {
|
2017-09-03 19:30:04 +00:00
|
|
|
return createRow(user);
|
|
|
|
}
|
|
|
|
return nullptr;
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
2017-08-17 08:31:24 +00:00
|
|
|
void ContactsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
2022-11-30 12:55:51 +00:00
|
|
|
const auto peer = row->peer();
|
|
|
|
if (const auto window = peer->session().tryResolveWindow()) {
|
|
|
|
window->showPeerHistory(row->peer());
|
|
|
|
}
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
2021-12-22 15:14:16 +00:00
|
|
|
void ContactsBoxController::setSortMode(SortMode mode) {
|
|
|
|
if (_sortMode == mode) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_sortMode = mode;
|
|
|
|
sort();
|
|
|
|
if (_sortMode == SortMode::Online) {
|
|
|
|
session().changes().peerUpdates(
|
|
|
|
Data::PeerUpdate::Flag::OnlineStatus
|
|
|
|
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
|
|
|
return !_sortByOnlineTimer.isActive()
|
|
|
|
&& delegate()->peerListFindRow(update.peer->id.value);
|
|
|
|
}) | rpl::start_with_next([=] {
|
|
|
|
_sortByOnlineTimer.callOnce(kSortByOnlineThrottle);
|
|
|
|
}, _sortByOnlineLifetime);
|
|
|
|
} else {
|
|
|
|
_sortByOnlineTimer.cancel();
|
|
|
|
_sortByOnlineLifetime.destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ContactsBoxController::sort() {
|
|
|
|
switch (_sortMode) {
|
|
|
|
case SortMode::Alphabet: sortByName(); break;
|
|
|
|
case SortMode::Online: sortByOnline(); break;
|
|
|
|
default: Unexpected("SortMode in ContactsBoxController.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ContactsBoxController::sortByName() {
|
|
|
|
auto keys = base::flat_map<PeerListRowId, QString>();
|
|
|
|
keys.reserve(delegate()->peerListFullRowsCount());
|
|
|
|
const auto key = [&](const PeerListRow &row) {
|
|
|
|
const auto id = row.id();
|
|
|
|
const auto i = keys.find(id);
|
|
|
|
if (i != end(keys)) {
|
|
|
|
return i->second;
|
|
|
|
}
|
|
|
|
const auto peer = row.peer();
|
|
|
|
const auto history = peer->owner().history(peer);
|
|
|
|
return keys.emplace(id, history->chatListNameSortKey()).first->second;
|
|
|
|
};
|
|
|
|
const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
|
|
|
|
return (key(a).compare(key(b)) < 0);
|
|
|
|
};
|
|
|
|
delegate()->peerListSortRows(predicate);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ContactsBoxController::sortByOnline() {
|
|
|
|
const auto now = base::unixtime::now();
|
|
|
|
const auto key = [&](const PeerListRow &row) {
|
|
|
|
const auto user = row.peer()->asUser();
|
|
|
|
return user ? (std::min(user->onlineTill, now) + 1) : TimeId();
|
|
|
|
};
|
|
|
|
const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
|
|
|
|
return key(a) > key(b);
|
|
|
|
};
|
|
|
|
delegate()->peerListSortRows(predicate);
|
|
|
|
}
|
|
|
|
|
2017-08-17 08:31:24 +00:00
|
|
|
bool ContactsBoxController::appendRow(not_null<UserData*> user) {
|
2021-04-01 21:04:10 +00:00
|
|
|
if (auto row = delegate()->peerListFindRow(user->id.value)) {
|
2017-08-14 12:53:49 +00:00
|
|
|
updateRowHook(row);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (auto row = createRow(user)) {
|
|
|
|
delegate()->peerListAppendRow(std::move(row));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-12-21 19:43:23 +00:00
|
|
|
std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
|
|
|
|
not_null<UserData*> user) {
|
2017-08-14 12:53:49 +00:00
|
|
|
return std::make_unique<PeerListRow>(user);
|
|
|
|
}
|
2017-08-14 12:48:11 +00:00
|
|
|
|
2017-11-07 13:13:41 +00:00
|
|
|
ChooseRecipientBoxController::ChooseRecipientBoxController(
|
2020-12-21 19:43:23 +00:00
|
|
|
not_null<Main::Session*> session,
|
2022-11-01 04:46:31 +00:00
|
|
|
FnMut<void(not_null<Data::Thread*>)> callback,
|
|
|
|
Fn<bool(not_null<Data::Thread*>)> filter)
|
2020-12-21 19:43:23 +00:00
|
|
|
: ChatsListBoxController(session)
|
|
|
|
, _session(session)
|
2022-06-02 20:26:41 +00:00
|
|
|
, _callback(std::move(callback))
|
|
|
|
, _filter(std::move(filter)) {
|
2019-07-25 18:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Main::Session &ChooseRecipientBoxController::session() const {
|
2020-12-21 19:43:23 +00:00
|
|
|
return *_session;
|
2017-11-07 13:13:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChooseRecipientBoxController::prepareViewHook() {
|
2019-06-18 15:00:55 +00:00
|
|
|
delegate()->peerListSetTitle(tr::lng_forward_choose());
|
2017-11-07 13:13:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
2022-11-01 04:46:31 +00:00
|
|
|
auto guard = base::make_weak(this);
|
|
|
|
const auto peer = row->peer();
|
|
|
|
if (const auto forum = peer->forum()) {
|
|
|
|
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
|
|
|
auto callback = [=](not_null<Data::ForumTopic*> topic) {
|
|
|
|
const auto exists = guard.get();
|
|
|
|
if (!exists) {
|
|
|
|
if (*weak) {
|
|
|
|
(*weak)->closeBox();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto onstack = std::move(_callback);
|
|
|
|
onstack(topic);
|
|
|
|
if (guard) {
|
|
|
|
_callback = std::move(onstack);
|
|
|
|
} else if (*weak) {
|
|
|
|
(*weak)->closeBox();
|
|
|
|
}
|
|
|
|
};
|
2022-11-01 07:18:56 +00:00
|
|
|
const auto filter = [=](not_null<Data::ForumTopic*> topic) {
|
|
|
|
return guard && (!_filter || _filter(topic));
|
|
|
|
};
|
2022-11-01 04:46:31 +00:00
|
|
|
auto owned = Box<PeerListBox>(
|
|
|
|
std::make_unique<ChooseTopicBoxController>(
|
|
|
|
forum,
|
2022-11-01 07:18:56 +00:00
|
|
|
std::move(callback),
|
|
|
|
filter),
|
2022-11-01 04:46:31 +00:00
|
|
|
[=](not_null<PeerListBox*> box) {
|
2022-11-01 07:18:56 +00:00
|
|
|
box->addButton(tr::lng_cancel(), [=] {
|
|
|
|
box->closeBox();
|
|
|
|
});
|
|
|
|
|
|
|
|
forum->destroyed(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
box->closeBox();
|
|
|
|
}, box->lifetime());
|
2022-11-01 04:46:31 +00:00
|
|
|
});
|
|
|
|
*weak = owned.data();
|
|
|
|
delegate()->peerListShowBox(std::move(owned));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto history = peer->owner().history(peer);
|
2018-11-30 05:45:22 +00:00
|
|
|
auto callback = std::move(_callback);
|
2022-11-01 04:46:31 +00:00
|
|
|
callback(history);
|
|
|
|
if (guard) {
|
2018-11-30 05:45:22 +00:00
|
|
|
_callback = std::move(callback);
|
|
|
|
}
|
2017-11-07 13:13:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
auto ChooseRecipientBoxController::createRow(
|
|
|
|
not_null<History*> history) -> std::unique_ptr<Row> {
|
2020-05-08 09:35:16 +00:00
|
|
|
const auto peer = history->peer;
|
2022-06-02 20:26:41 +00:00
|
|
|
const auto skip = _filter
|
2022-11-01 04:46:31 +00:00
|
|
|
? !_filter(history)
|
2022-06-02 20:26:41 +00:00
|
|
|
: ((peer->isBroadcast() && !peer->canWrite())
|
2022-11-15 14:21:44 +00:00
|
|
|
|| (peer->isUser() && !peer->canWrite())
|
2022-06-02 20:26:41 +00:00
|
|
|
|| peer->isRepliesChat());
|
2020-05-08 09:35:16 +00:00
|
|
|
return skip ? nullptr : std::make_unique<Row>(history);
|
2017-11-07 13:13:41 +00:00
|
|
|
}
|
2022-11-01 04:46:31 +00:00
|
|
|
|
|
|
|
ChooseTopicSearchController::ChooseTopicSearchController(
|
|
|
|
not_null<Data::Forum*> forum)
|
|
|
|
: _forum(forum)
|
|
|
|
, _api(&forum->session().mtp())
|
|
|
|
, _timer([=] { searchOnServer(); }) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChooseTopicSearchController::searchQuery(const QString &query) {
|
|
|
|
if (_query != query) {
|
|
|
|
_query = query;
|
|
|
|
_api.request(base::take(_requestId)).cancel();
|
|
|
|
_offsetDate = 0;
|
|
|
|
_offsetId = 0;
|
|
|
|
_offsetTopicId = 0;
|
|
|
|
_allLoaded = false;
|
|
|
|
if (!_query.isEmpty()) {
|
|
|
|
_timer.callOnce(AutoSearchTimeout);
|
|
|
|
} else {
|
|
|
|
_timer.cancel();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChooseTopicSearchController::searchOnServer() {
|
|
|
|
_requestId = _api.request(MTPchannels_GetForumTopics(
|
|
|
|
MTP_flags(MTPchannels_GetForumTopics::Flag::f_q),
|
|
|
|
_forum->channel()->inputChannel,
|
|
|
|
MTP_string(_query),
|
|
|
|
MTP_int(_offsetDate),
|
|
|
|
MTP_int(_offsetId),
|
|
|
|
MTP_int(_offsetTopicId),
|
|
|
|
MTP_int(kSearchPerPage)
|
|
|
|
)).done([=](const MTPmessages_ForumTopics &result) {
|
|
|
|
_requestId = 0;
|
|
|
|
const auto savedTopicId = _offsetTopicId;
|
|
|
|
const auto byCreation = result.data().is_order_by_create_date();
|
|
|
|
_forum->applyReceivedTopics(result, [&](
|
|
|
|
not_null<Data::ForumTopic*> topic) {
|
|
|
|
_offsetTopicId = topic->rootId();
|
|
|
|
if (byCreation) {
|
|
|
|
_offsetDate = topic->creationDate();
|
|
|
|
if (const auto last = topic->lastServerMessage()) {
|
|
|
|
_offsetId = last->id;
|
|
|
|
}
|
|
|
|
} else if (const auto last = topic->lastServerMessage()) {
|
|
|
|
_offsetId = last->id;
|
|
|
|
_offsetDate = last->date();
|
|
|
|
}
|
|
|
|
delegate()->peerListSearchAddRow(topic->rootId().bare);
|
|
|
|
});
|
2023-01-20 09:08:37 +00:00
|
|
|
if (_offsetTopicId == savedTopicId) {
|
2022-11-01 04:46:31 +00:00
|
|
|
_allLoaded = true;
|
|
|
|
}
|
2023-01-20 09:08:37 +00:00
|
|
|
delegate()->peerListSearchRefreshRows();
|
2022-11-01 04:46:31 +00:00
|
|
|
}).fail([=] {
|
|
|
|
_allLoaded = true;
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ChooseTopicSearchController::isLoading() {
|
|
|
|
return _timer.isActive() || _requestId;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ChooseTopicSearchController::loadMoreRows() {
|
|
|
|
if (!isLoading()) {
|
|
|
|
searchOnServer();
|
|
|
|
}
|
|
|
|
return !_allLoaded;
|
|
|
|
}
|
|
|
|
|
2022-11-01 07:18:56 +00:00
|
|
|
ChooseTopicBoxController::Row::Row(not_null<Data::ForumTopic*> topic)
|
|
|
|
: PeerListRow(topic->rootId().bare)
|
|
|
|
, _topic(topic) {
|
|
|
|
}
|
|
|
|
|
|
|
|
QString ChooseTopicBoxController::Row::generateName() {
|
|
|
|
return _topic->title();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString ChooseTopicBoxController::Row::generateShortName() {
|
|
|
|
return _topic->title();
|
|
|
|
}
|
|
|
|
|
2022-12-06 08:12:07 +00:00
|
|
|
auto ChooseTopicBoxController::Row::generatePaintUserpicCallback(
|
|
|
|
bool forceRound)
|
2022-11-01 07:18:56 +00:00
|
|
|
-> PaintRoundImageCallback {
|
|
|
|
return [=](
|
|
|
|
Painter &p,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int outerWidth,
|
|
|
|
int size) {
|
2023-01-20 08:23:23 +00:00
|
|
|
const auto &st = st::forumTopicRow;
|
|
|
|
x -= st.padding.left();
|
|
|
|
y -= st.padding.top();
|
2022-12-05 12:18:10 +00:00
|
|
|
auto view = Ui::PeerUserpicView();
|
2022-11-01 07:18:56 +00:00
|
|
|
p.translate(x, y);
|
|
|
|
_topic->paintUserpic(p, view, {
|
2023-01-20 08:23:23 +00:00
|
|
|
.st = &st,
|
2022-12-06 16:32:58 +00:00
|
|
|
.currentBg = st::windowBg,
|
2022-11-01 07:18:56 +00:00
|
|
|
.now = crl::now(),
|
|
|
|
.width = outerWidth,
|
|
|
|
.paused = false,
|
|
|
|
});
|
|
|
|
p.translate(-x, -y);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
auto ChooseTopicBoxController::Row::generateNameFirstLetters() const
|
|
|
|
-> const base::flat_set<QChar> & {
|
|
|
|
return _topic->chatListFirstLetters();
|
|
|
|
}
|
|
|
|
|
|
|
|
auto ChooseTopicBoxController::Row::generateNameWords() const
|
|
|
|
-> const base::flat_set<QString> & {
|
|
|
|
return _topic->chatListNameWords();
|
|
|
|
}
|
|
|
|
|
2022-11-01 04:46:31 +00:00
|
|
|
ChooseTopicBoxController::ChooseTopicBoxController(
|
|
|
|
not_null<Data::Forum*> forum,
|
|
|
|
FnMut<void(not_null<Data::ForumTopic*>)> callback,
|
|
|
|
Fn<bool(not_null<Data::ForumTopic*>)> filter)
|
|
|
|
: PeerListController(std::make_unique<ChooseTopicSearchController>(forum))
|
|
|
|
, _forum(forum)
|
|
|
|
, _callback(std::move(callback))
|
|
|
|
, _filter(std::move(filter)) {
|
|
|
|
setStyleOverrides(&st::chooseTopicList);
|
|
|
|
|
|
|
|
_forum->chatsListChanges(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
refreshRows();
|
|
|
|
}, lifetime());
|
|
|
|
|
|
|
|
_forum->topicDestroyed(
|
|
|
|
) | rpl::start_with_next([=](not_null<Data::ForumTopic*> topic) {
|
|
|
|
const auto id = PeerListRowId(topic->rootId().bare);
|
|
|
|
if (const auto row = delegate()->peerListFindRow(id)) {
|
|
|
|
delegate()->peerListRemoveRow(row);
|
|
|
|
delegate()->peerListRefreshRows();
|
|
|
|
}
|
|
|
|
}, lifetime());
|
|
|
|
}
|
|
|
|
|
|
|
|
Main::Session &ChooseTopicBoxController::session() const {
|
|
|
|
return _forum->session();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChooseTopicBoxController::rowClicked(not_null<PeerListRow*> row) {
|
|
|
|
const auto weak = base::make_weak(this);
|
|
|
|
auto onstack = base::take(_callback);
|
|
|
|
onstack(static_cast<Row*>(row.get())->topic());
|
|
|
|
if (weak) {
|
|
|
|
_callback = std::move(onstack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChooseTopicBoxController::prepare() {
|
|
|
|
delegate()->peerListSetTitle(tr::lng_forward_choose());
|
2023-01-20 09:08:37 +00:00
|
|
|
setSearchNoResultsText(tr::lng_topics_not_found(tr::now));
|
2022-11-01 04:46:31 +00:00
|
|
|
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
|
|
|
refreshRows(true);
|
2022-11-01 12:20:51 +00:00
|
|
|
|
|
|
|
session().changes().entryUpdates(
|
|
|
|
Data::EntryUpdate::Flag::Repaint
|
|
|
|
) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
|
|
|
|
if (const auto topic = update.entry->asTopic()) {
|
|
|
|
if (topic->forum() == _forum) {
|
|
|
|
const auto id = topic->rootId().bare;
|
|
|
|
if (const auto row = delegate()->peerListFindRow(id)) {
|
|
|
|
delegate()->peerListUpdateRow(row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, lifetime());
|
2022-11-01 04:46:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChooseTopicBoxController::refreshRows(bool initial) {
|
|
|
|
auto added = false;
|
|
|
|
for (const auto &row : _forum->topicsList()->indexed()->all()) {
|
|
|
|
if (const auto topic = row->topic()) {
|
|
|
|
const auto id = topic->rootId().bare;
|
|
|
|
auto already = delegate()->peerListFindRow(id);
|
|
|
|
if (initial || !already) {
|
2022-11-01 07:18:56 +00:00
|
|
|
if (auto created = createRow(topic)) {
|
|
|
|
delegate()->peerListAppendRow(std::move(created));
|
|
|
|
added = true;
|
|
|
|
}
|
2022-11-01 04:46:31 +00:00
|
|
|
} else if (already->isSearchResult()) {
|
|
|
|
delegate()->peerListAppendFoundRow(already);
|
|
|
|
added = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (added) {
|
|
|
|
delegate()->peerListRefreshRows();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChooseTopicBoxController::loadMoreRows() {
|
|
|
|
_forum->requestTopics();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
|
|
|
|
PeerListRowId id) {
|
|
|
|
if (const auto topic = _forum->topicFor(MsgId(id))) {
|
|
|
|
return std::make_unique<Row>(topic);
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
|
|
|
|
-> std::unique_ptr<Row> {
|
|
|
|
const auto skip = _filter ? !_filter(topic) : !topic->canWrite();
|
|
|
|
return skip ? nullptr : std::make_unique<Row>(topic);
|
|
|
|
};
|