tdesktop/Telegram/SourceFiles/boxes/peer_list_controllers.cpp

1095 lines
30 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peer_list_controllers.h"
#include "api/api_chat_participants.h"
#include "api/api_premium.h"
#include "base/random.h"
#include "boxes/filters/edit_filter_chats_list.h"
#include "settings/settings_premium.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/round_checkbox.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "main/main_session.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_folder.h"
#include "data/data_histories.h"
#include "data/data_changes.h"
#include "dialogs/ui/dialogs_layout.h"
#include "apiwrap.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "lang/lang_keys.h"
#include "history/history.h"
#include "history/history_item.h"
#include "dialogs/dialogs_main_list.h"
#include "ui/effects/outline_segments.h"
#include "ui/wrap/slide_wrap.h"
#include "window/window_session_controller.h" // showAddContact()
#include "base/unixtime.h"
#include "styles/style_boxes.h"
#include "styles/style_profile.h"
#include "styles/style_dialogs.h"
#include "styles/style_chat_helpers.h"
namespace {
constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000);
constexpr auto kSearchPerPage = 50;
} // namespace
object_ptr<Ui::BoxContent> PrepareContactsBox(
not_null<Window::SessionController*> sessionController) {
using Mode = ContactsBoxController::SortMode;
class Controller final : public ContactsBoxController {
public:
using ContactsBoxController::ContactsBoxController;
protected:
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override {
return !user->isSelf()
? ContactsBoxController::createRow(user)
: nullptr;
}
};
auto controller = std::make_unique<Controller>(
&sessionController->session());
controller->setStyleOverrides(&st::contactsWithStories);
controller->setStoriesShown(true);
const auto raw = controller.get();
auto init = [=](not_null<PeerListBox*> box) {
struct State {
QPointer<::Ui::IconButton> toggleSort;
rpl::variable<Mode> mode = Mode::Online;
::Ui::Animations::Simple scrollAnimation;
};
const auto state = box->lifetime().make_state<State>();
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
box->addLeftButton(
tr::lng_profile_add_contact(),
[=] { sessionController->showAddContact(); });
state->toggleSort = box->addTopButton(st::contactsSortButton, [=] {
const auto online = (state->mode.current() == Mode::Online);
const auto mode = online ? Mode::Alphabet : Mode::Online;
state->mode = mode;
raw->setSortMode(mode);
state->toggleSort->setIconOverride(
online ? &st::contactsSortOnlineIcon : nullptr,
online ? &st::contactsSortOnlineIconOver : nullptr);
});
raw->setSortMode(Mode::Online);
};
return Box<PeerListBox>(std::move(controller), std::move(init));
}
QBrush PeerListStoriesGradient(const style::PeerList &st) {
const auto left = st.item.photoPosition.x();
const auto top = st.item.photoPosition.y();
const auto size = st.item.photoSize;
return Ui::UnreadStoryOutlineGradient(QRectF(left, top, size, size));
}
std::vector<Ui::OutlineSegment> PeerListStoriesSegments(
int count,
int unread,
const QBrush &unreadBrush) {
Expects(unread <= count);
Expects(count > 0);
auto result = std::vector<Ui::OutlineSegment>();
const auto add = [&](bool unread) {
result.push_back({
.brush = unread ? unreadBrush : st::dialogsUnreadBgMuted->b,
.width = (unread
? st::dialogsStoriesFull.lineTwice / 2.
: st::dialogsStoriesFull.lineReadTwice / 2.),
});
};
result.reserve(count);
for (auto i = 0, till = count - unread; i != till; ++i) {
add(false);
}
for (auto i = 0; i != unread; ++i) {
add(true);
}
return result;
}
void PeerListRowWithLink::setActionLink(const QString &action) {
_action = action;
refreshActionLink();
}
void PeerListRowWithLink::refreshActionLink() {
if (!isInitialized()) return;
_actionWidth = _action.isEmpty() ? 0 : st::normalFont->width(_action);
}
void PeerListRowWithLink::lazyInitialize(const style::PeerListItem &st) {
PeerListRow::lazyInitialize(st);
refreshActionLink();
}
QSize PeerListRowWithLink::rightActionSize() const {
return QSize(_actionWidth, st::normalFont->height);
}
QMargins PeerListRowWithLink::rightActionMargins() const {
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);
}
void PeerListRowWithLink::rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) {
p.setFont(actionSelected ? st::linkFontOver : st::linkFont);
p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color);
p.drawTextLeft(x, y, outerWidth, _action, _actionWidth);
}
PeerListGlobalSearchController::PeerListGlobalSearchController(
not_null<Main::Session*> session)
: _session(session)
, _api(&session->mtp()) {
_timer.setCallback([this] { searchOnServer(); });
}
void PeerListGlobalSearchController::searchQuery(const QString &query) {
if (_query != 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 = _api.request(MTPcontacts_Search(
MTP_string(_query),
MTP_int(SearchPeopleLimit)
)).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) {
searchDone(result, requestId);
}).fail([=](const MTP::Error &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) {
_session->data().processUsers(contacts.vusers());
_session->data().processChats(contacts.vchats());
auto it = _queries.find(requestId);
if (it != _queries.cend()) {
query = it->second;
_cache[query] = result;
_queries.erase(it);
}
}
const auto feedList = [&](const MTPVector<MTPPeer> &list) {
for (const auto &mtpPeer : list.v) {
const auto peer = _session->data().peerLoaded(
peerFromMTP(mtpPeer));
if (peer) {
delegate()->peerListSearchAddRow(peer);
}
}
};
if (_requestId == requestId) {
_requestId = 0;
feedList(contacts.vmy_results());
feedList(contacts.vresults());
delegate()->peerListSearchRefreshRows();
}
}
bool PeerListGlobalSearchController::isLoading() {
return _timer.isActive() || _requestId;
}
RecipientRow::RecipientRow(
not_null<PeerData*> peer,
const style::PeerListItem *maybeLockedSt,
History *maybeHistory)
: PeerListRow(peer)
, _maybeHistory(maybeHistory)
, _resolvePremiumRequired(maybeLockedSt != nullptr) {
if (maybeLockedSt
&& (Api::ResolveRequiresPremiumToWrite(peer, maybeHistory)
== Api::RequirePremiumState::Yes)) {
_lockedSt = maybeLockedSt;
}
}
PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback(
bool forceRound) {
auto result = PeerListRow::generatePaintUserpicCallback(forceRound);
if (const auto st = _lockedSt) {
return [=](Painter &p, int x, int y, int outerWidth, int size) {
result(p, x, y, outerWidth, size);
PaintPremiumRequiredLock(p, st, x, y, outerWidth, size);
};
}
return result;
}
bool RecipientRow::refreshLock(
not_null<const style::PeerListItem*> maybeLockedSt) {
if (const auto user = peer()->asUser()) {
const auto locked = _resolvePremiumRequired
&& (Api::ResolveRequiresPremiumToWrite(user, _maybeHistory)
== Api::RequirePremiumState::Yes);
if (this->locked() != locked) {
setLocked(locked ? maybeLockedSt.get() : nullptr);
return true;
}
}
return false;
}
void RecipientRow::preloadUserpic() {
PeerListRow::preloadUserpic();
if (!_resolvePremiumRequired) {
return;
} else if (Api::ResolveRequiresPremiumToWrite(peer(), _maybeHistory)
== Api::RequirePremiumState::Unknown) {
const auto user = peer()->asUser();
user->session().api().premium().resolvePremiumRequired(user);
}
}
void TrackPremiumRequiredChanges(
not_null<PeerListController*> controller,
rpl::lifetime &lifetime) {
const auto session = &controller->session();
rpl::merge(
Data::AmPremiumValue(session) | rpl::to_empty,
session->api().premium().somePremiumRequiredResolved()
) | rpl::start_with_next([=] {
const auto st = &controller->computeListSt().item;
const auto delegate = controller->delegate();
const auto process = [&](not_null<PeerListRow*> raw) {
if (static_cast<RecipientRow*>(raw.get())->refreshLock(st)) {
delegate->peerListUpdateRow(raw);
}
};
auto count = delegate->peerListFullRowsCount();
for (auto i = 0; i != count; ++i) {
process(delegate->peerListRowAt(i));
}
count = delegate->peerListSearchRowsCount();
for (auto i = 0; i != count; ++i) {
process(delegate->peerListSearchRowAt(i));
}
}, lifetime);
}
ChatsListBoxController::Row::Row(
not_null<History*> history,
const style::PeerListItem *maybeLockedSt)
: RecipientRow(history->peer, maybeLockedSt, history) {
}
ChatsListBoxController::ChatsListBoxController(
not_null<Main::Session*> session)
: ChatsListBoxController(
std::make_unique<PeerListGlobalSearchController>(session)) {
}
ChatsListBoxController::ChatsListBoxController(
std::unique_ptr<PeerListSearchController> searchController)
: PeerListController(std::move(searchController)) {
}
void ChatsListBoxController::prepare() {
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
prepareViewHook();
if (!session().data().chatsListLoaded()) {
session().data().chatsListLoadedEvents(
) | rpl::filter([=](Data::Folder *folder) {
return !folder;
}) | rpl::start_with_next([=] {
checkForEmptyRows();
}, lifetime());
}
session().data().chatsListChanges(
) | rpl::start_with_next([=] {
rebuildRows();
}, lifetime());
session().data().contactsLoaded().value(
) | rpl::start_with_next([=] {
rebuildRows();
}, lifetime());
}
void ChatsListBoxController::rebuildRows() {
auto wasEmpty = !delegate()->peerListFullRowsCount();
auto appendList = [this](auto chats) {
auto count = 0;
for (const auto &row : chats->all()) {
if (const auto history = row->history()) {
if (appendRow(history)) {
++count;
}
}
}
return count;
};
auto added = 0;
if (!savedMessagesChatStatus().isEmpty()) {
if (appendRow(session().data().history(session().user()))) {
++added;
}
}
added += appendList(session().data().chatsList()->indexed());
const auto id = Data::Folder::kId;
if (const auto folder = session().data().folderLoaded(id)) {
added += appendList(folder->chatsList()->indexed());
}
added += appendList(session().data().contactsNoChatsList());
if (!wasEmpty && added > 0) {
// Place dialogs list before contactsNoDialogs list.
delegate()->peerListPartitionRows([](const PeerListRow &a) {
const auto history = static_cast<const Row&>(a).history();
return history->inChatList();
});
if (!savedMessagesChatStatus().isEmpty()) {
delegate()->peerListPartitionRows([](const PeerListRow &a) {
return a.peer()->isSelf();
});
}
}
checkForEmptyRows();
delegate()->peerListRefreshRows();
}
void ChatsListBoxController::checkForEmptyRows() {
if (delegate()->peerListFullRowsCount()) {
setDescriptionText(QString());
} else {
const auto loaded = session().data().contactsLoaded().current()
&& session().data().chatsListLoaded();
setDescriptionText(loaded ? emptyBoxText() : tr::lng_contacts_loading(tr::now));
}
}
QString ChatsListBoxController::emptyBoxText() const {
return tr::lng_contacts_not_found(tr::now);
}
std::unique_ptr<PeerListRow> ChatsListBoxController::createSearchRow(
not_null<PeerData*> peer) {
return createRow(peer->owner().history(peer));
}
bool ChatsListBoxController::appendRow(not_null<History*> history) {
if (auto row = delegate()->peerListFindRow(history->peer->id.value)) {
updateRowHook(static_cast<Row*>(row));
return false;
}
if (auto row = createRow(history)) {
delegate()->peerListAppendRow(std::move(row));
return true;
}
return false;
}
PeerListStories::PeerListStories(
not_null<PeerListController*> controller,
not_null<Main::Session*> session)
: _controller(controller)
, _session(session) {
}
void PeerListStories::updateColors() {
for (auto i = begin(_counts); i != end(_counts); ++i) {
if (const auto row = _delegate->peerListFindRow(i->first)) {
if (i->second.count >= 0 && i->second.unread >= 0) {
applyForRow(row, i->second.count, i->second.unread, true);
}
}
}
}
void PeerListStories::updateFor(
uint64 id,
int count,
int unread) {
if (const auto row = _delegate->peerListFindRow(id)) {
applyForRow(row, count, unread);
_delegate->peerListUpdateRow(row);
}
}
void PeerListStories::process(not_null<PeerListRow*> row) {
const auto user = row->peer()->asUser();
if (!user) {
return;
}
const auto stories = &_session->data().stories();
const auto source = stories->source(user->id);
const auto count = source
? int(source->ids.size())
: user->hasActiveStories()
? 1
: 0;
const auto unread = source
? source->info().unreadCount
: user->hasUnreadStories()
? 1
: 0;
applyForRow(row, count, unread, true);
}
bool PeerListStories::handleClick(not_null<PeerData*> peer) {
const auto point = _delegate->peerListLastRowMousePosition();
const auto &st = _controller->computeListSt().item;
if (point && point->x() < st.photoPosition.x() + st.photoSize) {
if (const auto window = peer->session().tryResolveWindow()) {
if (const auto user = peer->asUser()) {
if (user->hasActiveStories()) {
window->openPeerStories(peer->id);
return true;
}
}
}
}
return false;
}
void PeerListStories::prepare(not_null<PeerListDelegate*> delegate) {
_delegate = delegate;
_unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
style::PaletteChanged() | rpl::start_with_next([=] {
_unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
updateColors();
}, _lifetime);
_session->changes().peerUpdates(
Data::PeerUpdate::Flag::StoriesState
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
const auto id = update.peer->id.value;
if (const auto row = _delegate->peerListFindRow(id)) {
process(row);
}
}, _lifetime);
const auto stories = &_session->data().stories();
stories->sourceChanged() | rpl::start_with_next([=](PeerId id) {
const auto source = stories->source(id);
const auto info = source
? source->info()
: Data::StoriesSourceInfo();
updateFor(id.value, info.count, info.unreadCount);
}, _lifetime);
}
void PeerListStories::applyForRow(
not_null<PeerListRow*> row,
int count,
int unread,
bool force) {
auto &counts = _counts[row->id()];
if (!force && counts.count == count && counts.unread == unread) {
return;
}
counts.count = count;
counts.unread = unread;
_delegate->peerListSetRowChecked(row, count > 0);
if (count > 0) {
row->setCustomizedCheckSegments(
PeerListStoriesSegments(count, unread, _unreadBrush));
}
}
ContactsBoxController::ContactsBoxController(
not_null<Main::Session*> session)
: ContactsBoxController(
session,
std::make_unique<PeerListGlobalSearchController>(session)) {
}
ContactsBoxController::ContactsBoxController(
not_null<Main::Session*> session,
std::unique_ptr<PeerListSearchController> searchController)
: PeerListController(std::move(searchController))
, _session(session)
, _sortByOnlineTimer([=] { sort(); }) {
}
Main::Session &ContactsBoxController::session() const {
return *_session;
}
void ContactsBoxController::prepare() {
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
delegate()->peerListSetTitle(tr::lng_contacts_header());
prepareViewHook();
if (_stories) {
_stories->prepare(delegate());
}
session().data().contactsLoaded().value(
) | rpl::start_with_next([=] {
rebuildRows();
}, lifetime());
}
void ContactsBoxController::rebuildRows() {
const auto appendList = [&](auto chats) {
auto count = 0;
for (const auto &row : chats->all()) {
if (const auto history = row->history()) {
if (const auto user = history->peer->asUser()) {
if (appendRow(user)) {
++count;
}
}
}
}
return count;
};
appendList(session().data().contactsList());
checkForEmptyRows();
sort();
delegate()->peerListRefreshRows();
}
void ContactsBoxController::checkForEmptyRows() {
setDescriptionText(delegate()->peerListFullRowsCount()
? QString()
: session().data().contactsLoaded().current()
? tr::lng_contacts_not_found(tr::now)
: tr::lng_contacts_loading(tr::now));
}
std::unique_ptr<PeerListRow> ContactsBoxController::createSearchRow(
not_null<PeerData*> peer) {
if (const auto user = peer->asUser()) {
return createRow(user);
}
return nullptr;
}
void ContactsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto peer = row->peer();
if (_stories && _stories->handleClick(peer)) {
return;
} else if (const auto window = peer->session().tryResolveWindow()) {
window->showPeerHistory(peer);
}
}
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::setStoriesShown(bool shown) {
_stories = std::make_unique<PeerListStories>(this, _session);
}
void ContactsBoxController::sort() {
switch (_sortMode) {
case SortMode::Alphabet: sortByName(); break;
case SortMode::Online: sortByOnline(); break;
default: Unexpected("SortMode in ContactsBoxController.");
}
}
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->lastseen().onlineTill(), now + 1) + 1)
: TimeId();
};
const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
return key(a) > key(b);
};
delegate()->peerListSortRows(predicate);
}
bool ContactsBoxController::appendRow(not_null<UserData*> user) {
if (auto row = delegate()->peerListFindRow(user->id.value)) {
updateRowHook(row);
return false;
}
if (auto row = createRow(user)) {
const auto raw = row.get();
delegate()->peerListAppendRow(std::move(row));
if (_stories) {
_stories->process(raw);
}
return true;
}
return false;
}
std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
not_null<UserData*> user) {
return std::make_unique<PeerListRow>(user);
}
RecipientPremiumRequiredError WritePremiumRequiredError(
not_null<UserData*> user) {
return {
.text = tr::lng_send_non_premium_message_toast(
tr::now,
lt_user,
TextWithEntities{ user->shortName() },
lt_link,
Ui::Text::Link(
Ui::Text::Bold(
tr::lng_send_non_premium_message_toast_link(
tr::now))),
Ui::Text::RichLangValue),
};
}
ChooseRecipientBoxController::ChooseRecipientBoxController(
not_null<Main::Session*> session,
FnMut<void(not_null<Data::Thread*>)> callback,
Fn<bool(not_null<Data::Thread*>)> filter)
: ChooseRecipientBoxController({
.session = session,
.callback = std::move(callback),
.filter = std::move(filter),
}) {
}
ChooseRecipientBoxController::ChooseRecipientBoxController(
ChooseRecipientArgs &&args)
: ChatsListBoxController(args.session)
, _session(args.session)
, _callback(std::move(args.callback))
, _filter(std::move(args.filter))
, _premiumRequiredError(std::move(args.premiumRequiredError)) {
}
Main::Session &ChooseRecipientBoxController::session() const {
return *_session;
}
void ChooseRecipientBoxController::prepareViewHook() {
delegate()->peerListSetTitle(tr::lng_forward_choose());
if (_premiumRequiredError) {
TrackPremiumRequiredChanges(this, lifetime());
}
}
bool ChooseRecipientBoxController::showLockedError(
not_null<PeerListRow*> row) {
return RecipientRow::ShowLockedError(this, row, _premiumRequiredError);
}
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
if (showLockedError(row)) {
return;
}
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();
}
};
const auto filter = [=](not_null<Data::ForumTopic*> topic) {
return guard && (!_filter || _filter(topic));
};
auto owned = Box<PeerListBox>(
std::make_unique<ChooseTopicBoxController>(
forum,
std::move(callback),
filter),
[=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
forum->destroyed(
) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
});
*weak = owned.data();
delegate()->peerListUiShow()->showBox(std::move(owned));
return;
}
const auto history = peer->owner().history(peer);
auto callback = std::move(_callback);
callback(history);
if (guard) {
_callback = std::move(callback);
}
}
bool RecipientRow::ShowLockedError(
not_null<PeerListController*> controller,
not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error) {
if (!static_cast<RecipientRow*>(row.get())->locked()) {
return false;
}
::Settings::ShowPremiumPromoToast(
controller->delegate()->peerListUiShow(),
ChatHelpers::ResolveWindowDefault(),
error(row->peer()->asUser()).text,
u"require_premium"_q);
return true;
}
QString ChooseRecipientBoxController::savedMessagesChatStatus() const {
return tr::lng_saved_forward_here(tr::now);
}
auto ChooseRecipientBoxController::createRow(
not_null<History*> history) -> std::unique_ptr<Row> {
const auto peer = history->peer;
const auto skip = _filter
? !_filter(history)
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|| peer->isRepliesChat()
|| (peer->isUser() && (_premiumRequiredError
? !peer->asUser()->canSendIgnoreRequirePremium()
: !Data::CanSendAnything(peer))));
if (skip) {
return nullptr;
}
auto result = std::make_unique<Row>(
history,
_premiumRequiredError ? &computeListSt().item : nullptr);
return result;
}
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);
});
if (_offsetTopicId == savedTopicId) {
_allLoaded = true;
}
delegate()->peerListSearchRefreshRows();
}).fail([=] {
_allLoaded = true;
}).send();
}
bool ChooseTopicSearchController::isLoading() {
return _timer.isActive() || _requestId;
}
bool ChooseTopicSearchController::loadMoreRows() {
if (!isLoading()) {
searchOnServer();
}
return !_allLoaded;
}
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();
}
auto ChooseTopicBoxController::Row::generatePaintUserpicCallback(
bool forceRound)
-> PaintRoundImageCallback {
return [=](
Painter &p,
int x,
int y,
int outerWidth,
int size) {
const auto &st = st::forumTopicRow;
x -= st.padding.left();
y -= st.padding.top();
auto view = Ui::PeerUserpicView();
p.translate(x, y);
_topic->paintUserpic(p, view, {
.st = &st,
.currentBg = st::windowBg,
.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();
}
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());
setSearchNoResultsText(tr::lng_topics_not_found(tr::now));
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
refreshRows(true);
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());
}
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) {
if (auto created = createRow(topic)) {
delegate()->peerListAppendRow(std::move(created));
added = true;
}
} 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);
return skip ? nullptr : std::make_unique<Row>(topic);
};
void PaintPremiumRequiredLock(
Painter &p,
not_null<const style::PeerListItem*> st,
int x,
int y,
int outerWidth,
int size) {
auto hq = PainterHighQualityEnabler(p);
const auto &check = st->checkbox.check;
auto pen = check.border->p;
pen.setWidthF(check.width);
p.setPen(pen);
p.setBrush(st::premiumButtonBg2);
const auto &icon = st::stickersPremiumLock;
const auto width = icon.width();
const auto height = icon.height();
const auto rect = QRect(
QPoint(x + size - width, y + size - height),
icon.size());
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
}