peer_list_controllers.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "boxes/peer_list_controllers.h"
  8. #include "api/api_chat_participants.h"
  9. #include "api/api_premium.h" // MessageMoneyRestriction.
  10. #include "base/random.h"
  11. #include "boxes/filters/edit_filter_chats_list.h"
  12. #include "settings/settings_premium.h"
  13. #include "ui/boxes/confirm_box.h"
  14. #include "ui/effects/round_checkbox.h"
  15. #include "ui/text/text_utilities.h"
  16. #include "ui/widgets/menu/menu_add_action_callback_factory.h"
  17. #include "ui/widgets/checkbox.h"
  18. #include "ui/widgets/popup_menu.h"
  19. #include "ui/wrap/padding_wrap.h"
  20. #include "ui/painter.h"
  21. #include "ui/ui_utility.h"
  22. #include "main/main_session.h"
  23. #include "data/data_peer_values.h"
  24. #include "data/data_session.h"
  25. #include "data/data_stories.h"
  26. #include "data/data_channel.h"
  27. #include "data/data_chat.h"
  28. #include "data/data_user.h"
  29. #include "data/data_forum.h"
  30. #include "data/data_forum_topic.h"
  31. #include "data/data_folder.h"
  32. #include "data/data_histories.h"
  33. #include "data/data_changes.h"
  34. #include "dialogs/ui/dialogs_layout.h"
  35. #include "apiwrap.h"
  36. #include "mainwidget.h"
  37. #include "mainwindow.h"
  38. #include "lang/lang_keys.h"
  39. #include "history/history.h"
  40. #include "history/history_item.h"
  41. #include "dialogs/dialogs_main_list.h"
  42. #include "payments/ui/payments_reaction_box.h"
  43. #include "ui/effects/outline_segments.h"
  44. #include "ui/wrap/slide_wrap.h"
  45. #include "window/window_separate_id.h"
  46. #include "window/window_session_controller.h" // showAddContact()
  47. #include "base/unixtime.h"
  48. #include "styles/style_boxes.h"
  49. #include "styles/style_profile.h"
  50. #include "styles/style_dialogs.h"
  51. #include "styles/style_chat_helpers.h"
  52. namespace {
  53. constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000);
  54. constexpr auto kSearchPerPage = 50;
  55. } // namespace
  56. object_ptr<Ui::BoxContent> PrepareContactsBox(
  57. not_null<Window::SessionController*> sessionController) {
  58. using Mode = ContactsBoxController::SortMode;
  59. class Controller final : public ContactsBoxController {
  60. public:
  61. using ContactsBoxController::ContactsBoxController;
  62. [[nodiscard]] rpl::producer<not_null<PeerData*>> wheelClicks() const {
  63. return _wheelClicks.events();
  64. }
  65. protected:
  66. std::unique_ptr<PeerListRow> createRow(
  67. not_null<UserData*> user) override {
  68. return !user->isSelf()
  69. ? ContactsBoxController::createRow(user)
  70. : nullptr;
  71. }
  72. void rowMiddleClicked(
  73. not_null<PeerListRow*> row) override {
  74. _wheelClicks.fire(row->peer());
  75. }
  76. private:
  77. rpl::event_stream<not_null<PeerData*>> _wheelClicks;
  78. };
  79. auto controller = std::make_unique<Controller>(
  80. &sessionController->session());
  81. controller->setStyleOverrides(&st::contactsWithStories);
  82. controller->setStoriesShown(true);
  83. const auto raw = controller.get();
  84. auto init = [=](not_null<PeerListBox*> box) {
  85. struct State {
  86. QPointer<::Ui::IconButton> toggleSort;
  87. rpl::variable<Mode> mode = Mode::Online;
  88. ::Ui::Animations::Simple scrollAnimation;
  89. };
  90. const auto state = box->lifetime().make_state<State>();
  91. box->addButton(tr::lng_close(), [=] { box->closeBox(); });
  92. box->addLeftButton(
  93. tr::lng_profile_add_contact(),
  94. [=] { sessionController->showAddContact(); });
  95. state->toggleSort = box->addTopButton(st::contactsSortButton, [=] {
  96. const auto online = (state->mode.current() == Mode::Online);
  97. const auto mode = online ? Mode::Alphabet : Mode::Online;
  98. state->mode = mode;
  99. raw->setSortMode(mode);
  100. state->toggleSort->setIconOverride(
  101. online ? &st::contactsSortOnlineIcon : nullptr,
  102. online ? &st::contactsSortOnlineIconOver : nullptr);
  103. });
  104. raw->setSortMode(Mode::Online);
  105. raw->wheelClicks() | rpl::start_with_next([=](not_null<PeerData*> p) {
  106. sessionController->showInNewWindow(p);
  107. }, box->lifetime());
  108. };
  109. return Box<PeerListBox>(std::move(controller), std::move(init));
  110. }
  111. QBrush PeerListStoriesGradient(const style::PeerList &st) {
  112. const auto left = st.item.photoPosition.x();
  113. const auto top = st.item.photoPosition.y();
  114. const auto size = st.item.photoSize;
  115. return Ui::UnreadStoryOutlineGradient(QRectF(left, top, size, size));
  116. }
  117. std::vector<Ui::OutlineSegment> PeerListStoriesSegments(
  118. int count,
  119. int unread,
  120. const QBrush &unreadBrush) {
  121. Expects(unread <= count);
  122. Expects(count > 0);
  123. auto result = std::vector<Ui::OutlineSegment>();
  124. const auto add = [&](bool unread) {
  125. result.push_back({
  126. .brush = unread ? unreadBrush : st::dialogsUnreadBgMuted->b,
  127. .width = (unread
  128. ? st::dialogsStoriesFull.lineTwice / 2.
  129. : st::dialogsStoriesFull.lineReadTwice / 2.),
  130. });
  131. };
  132. result.reserve(count);
  133. for (auto i = 0, till = count - unread; i != till; ++i) {
  134. add(false);
  135. }
  136. for (auto i = 0; i != unread; ++i) {
  137. add(true);
  138. }
  139. return result;
  140. }
  141. void PeerListRowWithLink::setActionLink(const QString &action) {
  142. _action = action;
  143. refreshActionLink();
  144. }
  145. void PeerListRowWithLink::refreshActionLink() {
  146. if (!isInitialized()) return;
  147. _actionWidth = _action.isEmpty() ? 0 : st::normalFont->width(_action);
  148. }
  149. void PeerListRowWithLink::lazyInitialize(const style::PeerListItem &st) {
  150. PeerListRow::lazyInitialize(st);
  151. refreshActionLink();
  152. }
  153. QSize PeerListRowWithLink::rightActionSize() const {
  154. return QSize(_actionWidth, st::normalFont->height);
  155. }
  156. QMargins PeerListRowWithLink::rightActionMargins() const {
  157. return QMargins(
  158. st::contactsCheckPosition.x(),
  159. (st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom() - st::normalFont->height) / 2,
  160. st::defaultPeerListItem.photoPosition.x() + st::contactsCheckPosition.x(),
  161. 0);
  162. }
  163. void PeerListRowWithLink::rightActionPaint(
  164. Painter &p,
  165. int x,
  166. int y,
  167. int outerWidth,
  168. bool selected,
  169. bool actionSelected) {
  170. p.setFont(actionSelected ? st::linkFontOver : st::linkFont);
  171. p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color);
  172. p.drawTextLeft(x, y, outerWidth, _action, _actionWidth);
  173. }
  174. PeerListGlobalSearchController::PeerListGlobalSearchController(
  175. not_null<Main::Session*> session)
  176. : _session(session)
  177. , _api(&session->mtp()) {
  178. _timer.setCallback([this] { searchOnServer(); });
  179. }
  180. void PeerListGlobalSearchController::searchQuery(const QString &query) {
  181. if (_query != query) {
  182. _query = query;
  183. _requestId = 0;
  184. if (!_query.isEmpty() && !searchInCache()) {
  185. _timer.callOnce(AutoSearchTimeout);
  186. } else {
  187. _timer.cancel();
  188. }
  189. }
  190. }
  191. bool PeerListGlobalSearchController::searchInCache() {
  192. auto it = _cache.find(_query);
  193. if (it != _cache.cend()) {
  194. _requestId = 0;
  195. searchDone(it->second, _requestId);
  196. return true;
  197. }
  198. return false;
  199. }
  200. void PeerListGlobalSearchController::searchOnServer() {
  201. _requestId = _api.request(MTPcontacts_Search(
  202. MTP_string(_query),
  203. MTP_int(SearchPeopleLimit)
  204. )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) {
  205. searchDone(result, requestId);
  206. }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
  207. if (_requestId == requestId) {
  208. _requestId = 0;
  209. delegate()->peerListSearchRefreshRows();
  210. }
  211. }).send();
  212. _queries.emplace(_requestId, _query);
  213. }
  214. void PeerListGlobalSearchController::searchDone(
  215. const MTPcontacts_Found &result,
  216. mtpRequestId requestId) {
  217. Expects(result.type() == mtpc_contacts_found);
  218. auto &contacts = result.c_contacts_found();
  219. auto query = _query;
  220. if (requestId) {
  221. _session->data().processUsers(contacts.vusers());
  222. _session->data().processChats(contacts.vchats());
  223. auto it = _queries.find(requestId);
  224. if (it != _queries.cend()) {
  225. query = it->second;
  226. _cache[query] = result;
  227. _queries.erase(it);
  228. }
  229. }
  230. const auto feedList = [&](const MTPVector<MTPPeer> &list) {
  231. for (const auto &mtpPeer : list.v) {
  232. const auto peer = _session->data().peerLoaded(
  233. peerFromMTP(mtpPeer));
  234. if (peer) {
  235. delegate()->peerListSearchAddRow(peer);
  236. }
  237. }
  238. };
  239. if (_requestId == requestId) {
  240. _requestId = 0;
  241. feedList(contacts.vmy_results());
  242. feedList(contacts.vresults());
  243. delegate()->peerListSearchRefreshRows();
  244. }
  245. }
  246. bool PeerListGlobalSearchController::isLoading() {
  247. return _timer.isActive() || _requestId;
  248. }
  249. struct RecipientRow::Restriction {
  250. Api::MessageMoneyRestriction value;
  251. RestrictionBadgeCache cache;
  252. };
  253. RecipientRow::RecipientRow(
  254. not_null<PeerData*> peer,
  255. const style::PeerListItem *maybeLockedSt,
  256. History *maybeHistory)
  257. : PeerListRow(peer)
  258. , _maybeHistory(maybeHistory)
  259. , _maybeLockedSt(maybeLockedSt) {
  260. if (_maybeLockedSt) {
  261. setRestriction(Api::ResolveMessageMoneyRestrictions(
  262. peer,
  263. maybeHistory));
  264. }
  265. }
  266. Api::MessageMoneyRestriction RecipientRow::restriction() const {
  267. return _restriction
  268. ? _restriction->value
  269. : Api::MessageMoneyRestriction();
  270. }
  271. void RecipientRow::setRestriction(Api::MessageMoneyRestriction restriction) {
  272. if (!restriction) {
  273. _restriction = nullptr;
  274. return;
  275. } else if (!_restriction) {
  276. _restriction = std::make_unique<Restriction>();
  277. }
  278. _restriction->value = restriction;
  279. }
  280. void RecipientRow::paintUserpicOverlay(
  281. Painter &p,
  282. const style::PeerListItem &st,
  283. int x,
  284. int y,
  285. int outerWidth) {
  286. if (const auto &r = _restriction) {
  287. PaintRestrictionBadge(
  288. p,
  289. _maybeLockedSt,
  290. r->value.starsPerMessage,
  291. r->cache,
  292. x,
  293. y,
  294. outerWidth,
  295. st.photoSize);
  296. }
  297. }
  298. bool RecipientRow::refreshLock(
  299. not_null<const style::PeerListItem*> maybeLockedSt) {
  300. if (const auto user = peer()->asUser()) {
  301. using Restriction = Api::MessageMoneyRestriction;
  302. const auto r = _maybeLockedSt
  303. ? Api::ResolveMessageMoneyRestrictions(
  304. user,
  305. _maybeHistory)
  306. : Restriction();
  307. if ((_restriction ? _restriction->value : Restriction()) != r) {
  308. setRestriction(r);
  309. return true;
  310. }
  311. }
  312. return false;
  313. }
  314. void RecipientRow::preloadUserpic() {
  315. PeerListRow::preloadUserpic();
  316. if (!_maybeLockedSt) {
  317. return;
  318. }
  319. const auto peer = this->peer();
  320. const auto known = Api::ResolveMessageMoneyRestrictions(
  321. peer,
  322. _maybeHistory).known;
  323. if (known) {
  324. return;
  325. } else if (const auto user = peer->asUser()) {
  326. const auto api = &user->session().api();
  327. api->premium().resolveMessageMoneyRestrictions(user);
  328. } else if (const auto group = peer->asChannel()) {
  329. group->updateFull();
  330. }
  331. }
  332. void TrackMessageMoneyRestrictionsChanges(
  333. not_null<PeerListController*> controller,
  334. rpl::lifetime &lifetime) {
  335. const auto session = &controller->session();
  336. rpl::merge(
  337. Data::AmPremiumValue(session) | rpl::to_empty,
  338. session->api().premium().someMessageMoneyRestrictionsResolved()
  339. ) | rpl::start_with_next([=] {
  340. const auto st = &controller->computeListSt().item;
  341. const auto delegate = controller->delegate();
  342. const auto process = [&](not_null<PeerListRow*> raw) {
  343. if (static_cast<RecipientRow*>(raw.get())->refreshLock(st)) {
  344. delegate->peerListUpdateRow(raw);
  345. }
  346. };
  347. auto count = delegate->peerListFullRowsCount();
  348. for (auto i = 0; i != count; ++i) {
  349. process(delegate->peerListRowAt(i));
  350. }
  351. count = delegate->peerListSearchRowsCount();
  352. for (auto i = 0; i != count; ++i) {
  353. process(delegate->peerListSearchRowAt(i));
  354. }
  355. }, lifetime);
  356. }
  357. ChatsListBoxController::Row::Row(
  358. not_null<History*> history,
  359. const style::PeerListItem *maybeLockedSt)
  360. : RecipientRow(history->peer, maybeLockedSt, history) {
  361. }
  362. ChatsListBoxController::ChatsListBoxController(
  363. not_null<Main::Session*> session)
  364. : ChatsListBoxController(
  365. std::make_unique<PeerListGlobalSearchController>(session)) {
  366. }
  367. ChatsListBoxController::ChatsListBoxController(
  368. std::unique_ptr<PeerListSearchController> searchController)
  369. : PeerListController(std::move(searchController)) {
  370. }
  371. void ChatsListBoxController::prepare() {
  372. setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
  373. delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
  374. prepareViewHook();
  375. if (!session().data().chatsListLoaded()) {
  376. session().data().chatsListLoadedEvents(
  377. ) | rpl::filter([=](Data::Folder *folder) {
  378. return !folder;
  379. }) | rpl::start_with_next([=] {
  380. checkForEmptyRows();
  381. }, lifetime());
  382. }
  383. session().data().chatsListChanges(
  384. ) | rpl::start_with_next([=] {
  385. rebuildRows();
  386. }, lifetime());
  387. session().data().contactsLoaded().value(
  388. ) | rpl::start_with_next([=] {
  389. rebuildRows();
  390. }, lifetime());
  391. }
  392. void ChatsListBoxController::rebuildRows() {
  393. auto wasEmpty = !delegate()->peerListFullRowsCount();
  394. auto appendList = [this](auto chats) {
  395. auto count = 0;
  396. for (const auto &row : chats->all()) {
  397. if (const auto history = row->history()) {
  398. if (appendRow(history)) {
  399. ++count;
  400. }
  401. }
  402. }
  403. return count;
  404. };
  405. auto added = 0;
  406. if (!savedMessagesChatStatus().isEmpty()) {
  407. if (appendRow(session().data().history(session().user()))) {
  408. ++added;
  409. }
  410. }
  411. added += appendList(session().data().chatsList()->indexed());
  412. const auto id = Data::Folder::kId;
  413. if (const auto folder = session().data().folderLoaded(id)) {
  414. added += appendList(folder->chatsList()->indexed());
  415. }
  416. added += appendList(session().data().contactsNoChatsList());
  417. if (!wasEmpty && added > 0) {
  418. // Place dialogs list before contactsNoDialogs list.
  419. delegate()->peerListPartitionRows([](const PeerListRow &a) {
  420. const auto history = static_cast<const Row&>(a).history();
  421. return history->inChatList();
  422. });
  423. if (!savedMessagesChatStatus().isEmpty()) {
  424. delegate()->peerListPartitionRows([](const PeerListRow &a) {
  425. return a.peer()->isSelf();
  426. });
  427. }
  428. }
  429. checkForEmptyRows();
  430. delegate()->peerListRefreshRows();
  431. }
  432. void ChatsListBoxController::checkForEmptyRows() {
  433. if (delegate()->peerListFullRowsCount()) {
  434. setDescriptionText(QString());
  435. } else {
  436. const auto loaded = session().data().contactsLoaded().current()
  437. && session().data().chatsListLoaded();
  438. setDescriptionText(loaded ? emptyBoxText() : tr::lng_contacts_loading(tr::now));
  439. }
  440. }
  441. QString ChatsListBoxController::emptyBoxText() const {
  442. return tr::lng_contacts_not_found(tr::now);
  443. }
  444. std::unique_ptr<PeerListRow> ChatsListBoxController::createSearchRow(
  445. not_null<PeerData*> peer) {
  446. return createRow(peer->owner().history(peer));
  447. }
  448. bool ChatsListBoxController::appendRow(not_null<History*> history) {
  449. if (auto row = delegate()->peerListFindRow(history->peer->id.value)) {
  450. updateRowHook(static_cast<Row*>(row));
  451. return false;
  452. }
  453. if (auto row = createRow(history)) {
  454. delegate()->peerListAppendRow(std::move(row));
  455. return true;
  456. }
  457. return false;
  458. }
  459. PeerListStories::PeerListStories(
  460. not_null<PeerListController*> controller,
  461. not_null<Main::Session*> session)
  462. : _controller(controller)
  463. , _session(session) {
  464. }
  465. void PeerListStories::updateColors() {
  466. for (auto i = begin(_counts); i != end(_counts); ++i) {
  467. if (const auto row = _delegate->peerListFindRow(i->first)) {
  468. if (i->second.count >= 0 && i->second.unread >= 0) {
  469. applyForRow(row, i->second.count, i->second.unread, true);
  470. }
  471. }
  472. }
  473. }
  474. void PeerListStories::updateFor(
  475. uint64 id,
  476. int count,
  477. int unread) {
  478. if (const auto row = _delegate->peerListFindRow(id)) {
  479. applyForRow(row, count, unread);
  480. _delegate->peerListUpdateRow(row);
  481. }
  482. }
  483. void PeerListStories::process(not_null<PeerListRow*> row) {
  484. const auto user = row->peer()->asUser();
  485. if (!user) {
  486. return;
  487. }
  488. const auto stories = &_session->data().stories();
  489. const auto source = stories->source(user->id);
  490. const auto count = source
  491. ? int(source->ids.size())
  492. : user->hasActiveStories()
  493. ? 1
  494. : 0;
  495. const auto unread = source
  496. ? source->info().unreadCount
  497. : user->hasUnreadStories()
  498. ? 1
  499. : 0;
  500. applyForRow(row, count, unread, true);
  501. }
  502. bool PeerListStories::handleClick(not_null<PeerData*> peer) {
  503. const auto point = _delegate->peerListLastRowMousePosition();
  504. const auto &st = _controller->computeListSt().item;
  505. if (point && point->x() < st.photoPosition.x() + st.photoSize) {
  506. if (const auto window = peer->session().tryResolveWindow()) {
  507. if (const auto user = peer->asUser()) {
  508. if (user->hasActiveStories()) {
  509. window->openPeerStories(peer->id);
  510. return true;
  511. }
  512. }
  513. }
  514. }
  515. return false;
  516. }
  517. void PeerListStories::prepare(not_null<PeerListDelegate*> delegate) {
  518. _delegate = delegate;
  519. _unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
  520. style::PaletteChanged() | rpl::start_with_next([=] {
  521. _unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
  522. updateColors();
  523. }, _lifetime);
  524. _session->changes().peerUpdates(
  525. Data::PeerUpdate::Flag::StoriesState
  526. ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
  527. const auto id = update.peer->id.value;
  528. if (const auto row = _delegate->peerListFindRow(id)) {
  529. process(row);
  530. }
  531. }, _lifetime);
  532. const auto stories = &_session->data().stories();
  533. stories->sourceChanged() | rpl::start_with_next([=](PeerId id) {
  534. const auto source = stories->source(id);
  535. const auto info = source
  536. ? source->info()
  537. : Data::StoriesSourceInfo();
  538. updateFor(id.value, info.count, info.unreadCount);
  539. }, _lifetime);
  540. }
  541. void PeerListStories::applyForRow(
  542. not_null<PeerListRow*> row,
  543. int count,
  544. int unread,
  545. bool force) {
  546. auto &counts = _counts[row->id()];
  547. if (!force && counts.count == count && counts.unread == unread) {
  548. return;
  549. }
  550. counts.count = count;
  551. counts.unread = unread;
  552. _delegate->peerListSetRowChecked(row, count > 0);
  553. if (count > 0) {
  554. row->setCustomizedCheckSegments(
  555. PeerListStoriesSegments(count, unread, _unreadBrush));
  556. }
  557. }
  558. ContactsBoxController::ContactsBoxController(
  559. not_null<Main::Session*> session)
  560. : ContactsBoxController(
  561. session,
  562. std::make_unique<PeerListGlobalSearchController>(session)) {
  563. }
  564. ContactsBoxController::ContactsBoxController(
  565. not_null<Main::Session*> session,
  566. std::unique_ptr<PeerListSearchController> searchController)
  567. : PeerListController(std::move(searchController))
  568. , _session(session)
  569. , _sortByOnlineTimer([=] { sort(); }) {
  570. }
  571. Main::Session &ContactsBoxController::session() const {
  572. return *_session;
  573. }
  574. void ContactsBoxController::prepare() {
  575. setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
  576. delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
  577. delegate()->peerListSetTitle(tr::lng_contacts_header());
  578. prepareViewHook();
  579. if (_stories) {
  580. _stories->prepare(delegate());
  581. }
  582. session().data().contactsLoaded().value(
  583. ) | rpl::start_with_next([=] {
  584. rebuildRows();
  585. }, lifetime());
  586. }
  587. void ContactsBoxController::rebuildRows() {
  588. const auto appendList = [&](auto chats) {
  589. auto count = 0;
  590. for (const auto &row : chats->all()) {
  591. if (const auto history = row->history()) {
  592. if (const auto user = history->peer->asUser()) {
  593. if (appendRow(user)) {
  594. ++count;
  595. }
  596. }
  597. }
  598. }
  599. return count;
  600. };
  601. appendList(session().data().contactsList());
  602. checkForEmptyRows();
  603. sort();
  604. delegate()->peerListRefreshRows();
  605. }
  606. void ContactsBoxController::checkForEmptyRows() {
  607. setDescriptionText(delegate()->peerListFullRowsCount()
  608. ? QString()
  609. : session().data().contactsLoaded().current()
  610. ? tr::lng_contacts_not_found(tr::now)
  611. : tr::lng_contacts_loading(tr::now));
  612. }
  613. std::unique_ptr<PeerListRow> ContactsBoxController::createSearchRow(
  614. not_null<PeerData*> peer) {
  615. if (const auto user = peer->asUser()) {
  616. return createRow(user);
  617. }
  618. return nullptr;
  619. }
  620. void ContactsBoxController::rowClicked(not_null<PeerListRow*> row) {
  621. const auto peer = row->peer();
  622. if (_stories && _stories->handleClick(peer)) {
  623. return;
  624. } else if (const auto window = peer->session().tryResolveWindow()) {
  625. window->showPeerHistory(peer);
  626. }
  627. }
  628. void ContactsBoxController::setSortMode(SortMode mode) {
  629. if (_sortMode == mode) {
  630. return;
  631. }
  632. _sortMode = mode;
  633. sort();
  634. if (_sortMode == SortMode::Online) {
  635. session().changes().peerUpdates(
  636. Data::PeerUpdate::Flag::OnlineStatus
  637. ) | rpl::filter([=](const Data::PeerUpdate &update) {
  638. return !_sortByOnlineTimer.isActive()
  639. && delegate()->peerListFindRow(update.peer->id.value);
  640. }) | rpl::start_with_next([=] {
  641. _sortByOnlineTimer.callOnce(kSortByOnlineThrottle);
  642. }, _sortByOnlineLifetime);
  643. } else {
  644. _sortByOnlineTimer.cancel();
  645. _sortByOnlineLifetime.destroy();
  646. }
  647. }
  648. void ContactsBoxController::setStoriesShown(bool shown) {
  649. _stories = std::make_unique<PeerListStories>(this, _session);
  650. }
  651. void ContactsBoxController::sort() {
  652. switch (_sortMode) {
  653. case SortMode::Alphabet: sortByName(); break;
  654. case SortMode::Online: sortByOnline(); break;
  655. default: Unexpected("SortMode in ContactsBoxController.");
  656. }
  657. }
  658. void ContactsBoxController::sortByOnline() {
  659. const auto now = base::unixtime::now();
  660. const auto key = [&](const PeerListRow &row) {
  661. const auto user = row.peer()->asUser();
  662. return user
  663. ? (std::min(user->lastseen().onlineTill(), now + 1) + 1)
  664. : TimeId();
  665. };
  666. const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
  667. return key(a) > key(b);
  668. };
  669. delegate()->peerListSortRows(predicate);
  670. }
  671. bool ContactsBoxController::appendRow(not_null<UserData*> user) {
  672. if (auto row = delegate()->peerListFindRow(user->id.value)) {
  673. updateRowHook(row);
  674. return false;
  675. }
  676. if (auto row = createRow(user)) {
  677. const auto raw = row.get();
  678. delegate()->peerListAppendRow(std::move(row));
  679. if (_stories) {
  680. _stories->process(raw);
  681. }
  682. return true;
  683. }
  684. return false;
  685. }
  686. std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
  687. not_null<UserData*> user) {
  688. return std::make_unique<PeerListRow>(user);
  689. }
  690. RecipientMoneyRestrictionError WriteMoneyRestrictionError(
  691. not_null<UserData*> user) {
  692. return {
  693. .text = tr::lng_send_non_premium_message_toast(
  694. tr::now,
  695. lt_user,
  696. TextWithEntities{ user->shortName() },
  697. lt_link,
  698. Ui::Text::Link(
  699. Ui::Text::Bold(
  700. tr::lng_send_non_premium_message_toast_link(
  701. tr::now))),
  702. Ui::Text::RichLangValue),
  703. };
  704. }
  705. ChooseRecipientBoxController::ChooseRecipientBoxController(
  706. not_null<Main::Session*> session,
  707. FnMut<void(not_null<Data::Thread*>)> callback,
  708. Fn<bool(not_null<Data::Thread*>)> filter)
  709. : ChooseRecipientBoxController({
  710. .session = session,
  711. .callback = std::move(callback),
  712. .filter = std::move(filter),
  713. }) {
  714. }
  715. ChooseRecipientBoxController::ChooseRecipientBoxController(
  716. ChooseRecipientArgs &&args)
  717. : ChatsListBoxController(args.session)
  718. , _session(args.session)
  719. , _callback(std::move(args.callback))
  720. , _filter(std::move(args.filter))
  721. , _moneyRestrictionError(std::move(args.moneyRestrictionError)) {
  722. }
  723. Main::Session &ChooseRecipientBoxController::session() const {
  724. return *_session;
  725. }
  726. void ChooseRecipientBoxController::prepareViewHook() {
  727. delegate()->peerListSetTitle(tr::lng_forward_choose());
  728. if (_moneyRestrictionError) {
  729. TrackMessageMoneyRestrictionsChanges(this, lifetime());
  730. }
  731. }
  732. bool ChooseRecipientBoxController::showLockedError(
  733. not_null<PeerListRow*> row) {
  734. return RecipientRow::ShowLockedError(
  735. this,
  736. row,
  737. _moneyRestrictionError);
  738. }
  739. void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
  740. if (showLockedError(row)) {
  741. return;
  742. }
  743. auto guard = base::make_weak(this);
  744. const auto peer = row->peer();
  745. if (const auto forum = peer->forum()) {
  746. const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
  747. auto callback = [=](not_null<Data::ForumTopic*> topic) {
  748. const auto exists = guard.get();
  749. if (!exists) {
  750. if (*weak) {
  751. (*weak)->closeBox();
  752. }
  753. return;
  754. }
  755. auto onstack = std::move(_callback);
  756. onstack(topic);
  757. if (guard) {
  758. _callback = std::move(onstack);
  759. } else if (*weak) {
  760. (*weak)->closeBox();
  761. }
  762. };
  763. const auto filter = [=](not_null<Data::ForumTopic*> topic) {
  764. return guard && (!_filter || _filter(topic));
  765. };
  766. auto owned = Box<PeerListBox>(
  767. std::make_unique<ChooseTopicBoxController>(
  768. forum,
  769. std::move(callback),
  770. filter),
  771. [=](not_null<PeerListBox*> box) {
  772. box->addButton(tr::lng_cancel(), [=] {
  773. box->closeBox();
  774. });
  775. forum->destroyed(
  776. ) | rpl::start_with_next([=] {
  777. box->closeBox();
  778. }, box->lifetime());
  779. });
  780. *weak = owned.data();
  781. delegate()->peerListUiShow()->showBox(std::move(owned));
  782. return;
  783. }
  784. const auto history = peer->owner().history(peer);
  785. auto callback = std::move(_callback);
  786. callback(history);
  787. if (guard) {
  788. _callback = std::move(callback);
  789. }
  790. }
  791. bool RecipientRow::ShowLockedError(
  792. not_null<PeerListController*> controller,
  793. not_null<PeerListRow*> row,
  794. Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error) {
  795. const auto recipient = static_cast<RecipientRow*>(row.get());
  796. if (!recipient->restriction().premiumRequired) {
  797. return false;
  798. }
  799. ::Settings::ShowPremiumPromoToast(
  800. controller->delegate()->peerListUiShow(),
  801. ChatHelpers::ResolveWindowDefault(),
  802. error(row->peer()->asUser()).text,
  803. u"require_premium"_q);
  804. return true;
  805. }
  806. QString ChooseRecipientBoxController::savedMessagesChatStatus() const {
  807. return tr::lng_saved_forward_here(tr::now);
  808. }
  809. auto ChooseRecipientBoxController::createRow(
  810. not_null<History*> history) -> std::unique_ptr<Row> {
  811. const auto peer = history->peer;
  812. const auto skip = _filter
  813. ? !_filter(history)
  814. : ((peer->isBroadcast() && !Data::CanSendAnything(peer))
  815. || peer->isRepliesChat()
  816. || peer->isVerifyCodes()
  817. || (peer->isUser() && (_moneyRestrictionError
  818. ? !peer->asUser()->canSendIgnoreMoneyRestrictions()
  819. : !Data::CanSendAnything(peer))));
  820. if (skip) {
  821. return nullptr;
  822. }
  823. auto result = std::make_unique<Row>(
  824. history,
  825. _moneyRestrictionError ? &computeListSt().item : nullptr);
  826. return result;
  827. }
  828. ChooseTopicSearchController::ChooseTopicSearchController(
  829. not_null<Data::Forum*> forum)
  830. : _forum(forum)
  831. , _api(&forum->session().mtp())
  832. , _timer([=] { searchOnServer(); }) {
  833. }
  834. void ChooseTopicSearchController::searchQuery(const QString &query) {
  835. if (_query != query) {
  836. _query = query;
  837. _api.request(base::take(_requestId)).cancel();
  838. _offsetDate = 0;
  839. _offsetId = 0;
  840. _offsetTopicId = 0;
  841. _allLoaded = false;
  842. if (!_query.isEmpty()) {
  843. _timer.callOnce(AutoSearchTimeout);
  844. } else {
  845. _timer.cancel();
  846. }
  847. }
  848. }
  849. void ChooseTopicSearchController::searchOnServer() {
  850. _requestId = _api.request(MTPchannels_GetForumTopics(
  851. MTP_flags(MTPchannels_GetForumTopics::Flag::f_q),
  852. _forum->channel()->inputChannel,
  853. MTP_string(_query),
  854. MTP_int(_offsetDate),
  855. MTP_int(_offsetId),
  856. MTP_int(_offsetTopicId),
  857. MTP_int(kSearchPerPage)
  858. )).done([=](const MTPmessages_ForumTopics &result) {
  859. _requestId = 0;
  860. const auto savedTopicId = _offsetTopicId;
  861. const auto byCreation = result.data().is_order_by_create_date();
  862. _forum->applyReceivedTopics(result, [&](
  863. not_null<Data::ForumTopic*> topic) {
  864. _offsetTopicId = topic->rootId();
  865. if (byCreation) {
  866. _offsetDate = topic->creationDate();
  867. if (const auto last = topic->lastServerMessage()) {
  868. _offsetId = last->id;
  869. }
  870. } else if (const auto last = topic->lastServerMessage()) {
  871. _offsetId = last->id;
  872. _offsetDate = last->date();
  873. }
  874. delegate()->peerListSearchAddRow(topic->rootId().bare);
  875. });
  876. if (_offsetTopicId == savedTopicId) {
  877. _allLoaded = true;
  878. }
  879. delegate()->peerListSearchRefreshRows();
  880. }).fail([=] {
  881. _allLoaded = true;
  882. }).send();
  883. }
  884. bool ChooseTopicSearchController::isLoading() {
  885. return _timer.isActive() || _requestId;
  886. }
  887. bool ChooseTopicSearchController::loadMoreRows() {
  888. if (!isLoading()) {
  889. searchOnServer();
  890. }
  891. return !_allLoaded;
  892. }
  893. ChooseTopicBoxController::Row::Row(not_null<Data::ForumTopic*> topic)
  894. : PeerListRow(topic->rootId().bare)
  895. , _topic(topic) {
  896. }
  897. QString ChooseTopicBoxController::Row::generateName() {
  898. return _topic->title();
  899. }
  900. QString ChooseTopicBoxController::Row::generateShortName() {
  901. return _topic->title();
  902. }
  903. auto ChooseTopicBoxController::Row::generatePaintUserpicCallback(
  904. bool forceRound)
  905. -> PaintRoundImageCallback {
  906. return [=](
  907. Painter &p,
  908. int x,
  909. int y,
  910. int outerWidth,
  911. int size) {
  912. const auto &st = st::forumTopicRow;
  913. x -= st.padding.left();
  914. y -= st.padding.top();
  915. auto view = Ui::PeerUserpicView();
  916. p.translate(x, y);
  917. _topic->paintUserpic(p, view, {
  918. .st = &st,
  919. .currentBg = st::windowBg,
  920. .now = crl::now(),
  921. .width = outerWidth,
  922. .paused = false,
  923. });
  924. p.translate(-x, -y);
  925. };
  926. }
  927. auto ChooseTopicBoxController::Row::generateNameFirstLetters() const
  928. -> const base::flat_set<QChar> & {
  929. return _topic->chatListFirstLetters();
  930. }
  931. auto ChooseTopicBoxController::Row::generateNameWords() const
  932. -> const base::flat_set<QString> & {
  933. return _topic->chatListNameWords();
  934. }
  935. ChooseTopicBoxController::ChooseTopicBoxController(
  936. not_null<Data::Forum*> forum,
  937. FnMut<void(not_null<Data::ForumTopic*>)> callback,
  938. Fn<bool(not_null<Data::ForumTopic*>)> filter)
  939. : PeerListController(std::make_unique<ChooseTopicSearchController>(forum))
  940. , _forum(forum)
  941. , _callback(std::move(callback))
  942. , _filter(std::move(filter)) {
  943. setStyleOverrides(&st::chooseTopicList);
  944. _forum->chatsListChanges(
  945. ) | rpl::start_with_next([=] {
  946. refreshRows();
  947. }, lifetime());
  948. _forum->topicDestroyed(
  949. ) | rpl::start_with_next([=](not_null<Data::ForumTopic*> topic) {
  950. const auto id = PeerListRowId(topic->rootId().bare);
  951. if (const auto row = delegate()->peerListFindRow(id)) {
  952. delegate()->peerListRemoveRow(row);
  953. delegate()->peerListRefreshRows();
  954. }
  955. }, lifetime());
  956. }
  957. Main::Session &ChooseTopicBoxController::session() const {
  958. return _forum->session();
  959. }
  960. void ChooseTopicBoxController::rowClicked(not_null<PeerListRow*> row) {
  961. const auto weak = base::make_weak(this);
  962. auto onstack = base::take(_callback);
  963. onstack(static_cast<Row*>(row.get())->topic());
  964. if (weak) {
  965. _callback = std::move(onstack);
  966. }
  967. }
  968. void ChooseTopicBoxController::prepare() {
  969. delegate()->peerListSetTitle(tr::lng_forward_choose());
  970. setSearchNoResultsText(tr::lng_topics_not_found(tr::now));
  971. delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
  972. refreshRows(true);
  973. session().changes().entryUpdates(
  974. Data::EntryUpdate::Flag::Repaint
  975. ) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
  976. if (const auto topic = update.entry->asTopic()) {
  977. if (topic->forum() == _forum) {
  978. const auto id = topic->rootId().bare;
  979. if (const auto row = delegate()->peerListFindRow(id)) {
  980. delegate()->peerListUpdateRow(row);
  981. }
  982. }
  983. }
  984. }, lifetime());
  985. }
  986. void ChooseTopicBoxController::refreshRows(bool initial) {
  987. auto added = false;
  988. for (const auto &row : _forum->topicsList()->indexed()->all()) {
  989. if (const auto topic = row->topic()) {
  990. const auto id = topic->rootId().bare;
  991. auto already = delegate()->peerListFindRow(id);
  992. if (initial || !already) {
  993. if (auto created = createRow(topic)) {
  994. delegate()->peerListAppendRow(std::move(created));
  995. added = true;
  996. }
  997. } else if (already->isSearchResult()) {
  998. delegate()->peerListAppendFoundRow(already);
  999. added = true;
  1000. }
  1001. }
  1002. }
  1003. if (added) {
  1004. delegate()->peerListRefreshRows();
  1005. }
  1006. }
  1007. void ChooseTopicBoxController::loadMoreRows() {
  1008. _forum->requestTopics();
  1009. }
  1010. std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
  1011. PeerListRowId id) {
  1012. if (const auto topic = _forum->topicFor(MsgId(id))) {
  1013. return std::make_unique<Row>(topic);
  1014. }
  1015. return nullptr;
  1016. }
  1017. std::unique_ptr<PeerListRow> ChooseTopicBoxController::MakeRow(
  1018. not_null<Data::ForumTopic*> topic) {
  1019. return std::make_unique<Row>(topic);
  1020. }
  1021. auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
  1022. -> std::unique_ptr<Row> {
  1023. const auto skip = _filter && !_filter(topic);
  1024. return skip ? nullptr : std::make_unique<Row>(topic);
  1025. };
  1026. void PaintRestrictionBadge(
  1027. Painter &p,
  1028. not_null<const style::PeerListItem*> st,
  1029. int stars,
  1030. RestrictionBadgeCache &cache,
  1031. int x,
  1032. int y,
  1033. int outerWidth,
  1034. int size) {
  1035. const auto paletteVersion = style::PaletteVersion();
  1036. const auto good = !cache.badge.isNull()
  1037. && (cache.stars == stars)
  1038. && (cache.paletteVersion == paletteVersion);
  1039. const auto &check = st->checkbox.check;
  1040. const auto add = check.width;
  1041. if (!good) {
  1042. cache.stars = stars;
  1043. cache.paletteVersion = paletteVersion;
  1044. if (stars) {
  1045. const auto text = (stars >= 1000)
  1046. ? (QString::number(stars / 1000) + 'K')
  1047. : QString::number(stars);
  1048. cache.badge = Ui::GenerateSmallBadgeImage(
  1049. text,
  1050. st::paidReactTopStarIcon,
  1051. check.bgActive->c,
  1052. st::premiumButtonFg->c,
  1053. &check);
  1054. } else {
  1055. auto hq = PainterHighQualityEnabler(p);
  1056. const auto &icon = st::stickersPremiumLock;
  1057. const auto width = icon.width();
  1058. const auto height = icon.height();
  1059. const auto rect = QRect(
  1060. QPoint(x + size - width, y + size - height),
  1061. icon.size());
  1062. const auto added = QMargins(add, add, add, add);
  1063. const auto ratio = style::DevicePixelRatio();
  1064. cache.badge = QImage(
  1065. (rect + added).size() * ratio,
  1066. QImage::Format_ARGB32_Premultiplied);
  1067. cache.badge.setDevicePixelRatio(ratio);
  1068. cache.badge.fill(Qt::transparent);
  1069. const auto inner = QRect(add, add, rect.width(), rect.height());
  1070. auto q = QPainter(&cache.badge);
  1071. auto pen = check.border->p;
  1072. pen.setWidthF(check.width);
  1073. q.setPen(pen);
  1074. q.setBrush(st::premiumButtonBg2);
  1075. q.drawEllipse(inner);
  1076. icon.paintInCenter(q, inner);
  1077. }
  1078. }
  1079. const auto cached = cache.badge.size() / cache.badge.devicePixelRatio();
  1080. const auto left = x + size + add - cached.width();
  1081. const auto top = stars ? (y - add) : (y + size + add - cached.height());
  1082. p.drawImage(left, top, cache.badge);
  1083. }