info_similar_peers_widget.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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 "info/similar_peers/info_similar_peers_widget.h"
  8. #include "api/api_chat_participants.h"
  9. #include "apiwrap.h"
  10. #include "boxes/peer_list_box.h"
  11. #include "data/data_channel.h"
  12. #include "data/data_peer_values.h"
  13. #include "data/data_premium_limits.h"
  14. #include "data/data_session.h"
  15. #include "data/data_user.h"
  16. #include "info/info_controller.h"
  17. #include "main/main_session.h"
  18. #include "ui/text/text_utilities.h"
  19. #include "ui/widgets/buttons.h"
  20. #include "ui/widgets/scroll_area.h"
  21. #include "ui/widgets/tooltip.h"
  22. #include "ui/ui_utility.h"
  23. #include "lang/lang_keys.h"
  24. #include "settings/settings_premium.h"
  25. #include "window/window_session_controller.h"
  26. #include "styles/style_info.h"
  27. #include "styles/style_widgets.h"
  28. namespace Info::SimilarPeers {
  29. namespace {
  30. class ListController final : public PeerListController {
  31. public:
  32. ListController(
  33. not_null<Controller*> controller,
  34. not_null<PeerData*> peer);
  35. Main::Session &session() const override;
  36. void prepare() override;
  37. void rowClicked(not_null<PeerListRow*> row) override;
  38. void loadMoreRows() override;
  39. std::unique_ptr<PeerListRow> createRestoredRow(
  40. not_null<PeerData*> peer) override {
  41. return createRow(peer);
  42. }
  43. std::unique_ptr<PeerListState> saveState() const override;
  44. void restoreState(std::unique_ptr<PeerListState> state) override;
  45. void setContentWidget(not_null<Ui::RpWidget*> widget);
  46. [[nodiscard]] rpl::producer<int> unlockHeightValue() const;
  47. private:
  48. std::unique_ptr<PeerListRow> createRow(not_null<PeerData*> peer);
  49. void setupUnlock();
  50. void rebuild();
  51. struct SavedState : SavedStateBase {
  52. };
  53. const not_null<Controller*> _controller;
  54. const not_null<PeerData*> _peer;
  55. Ui::RpWidget *_content = nullptr;
  56. Ui::RpWidget *_unlock = nullptr;
  57. rpl::variable<int> _unlockHeight;
  58. };
  59. ListController::ListController(
  60. not_null<Controller*> controller,
  61. not_null<PeerData*> peer)
  62. : PeerListController()
  63. , _controller(controller)
  64. , _peer(peer) {
  65. }
  66. Main::Session &ListController::session() const {
  67. return _peer->session();
  68. }
  69. std::unique_ptr<PeerListRow> ListController::createRow(
  70. not_null<PeerData*> peer) {
  71. auto result = std::make_unique<PeerListRow>(peer);
  72. if (const auto channel = peer->asChannel()) {
  73. if (const auto count = channel->membersCount(); count > 1) {
  74. result->setCustomStatus(
  75. tr::lng_chat_status_subscribers(
  76. tr::now,
  77. lt_count_decimal,
  78. count));
  79. }
  80. }
  81. return result;
  82. }
  83. void ListController::prepare() {
  84. delegate()->peerListSetTitle(_peer->isBroadcast()
  85. ? tr::lng_similar_channels_title()
  86. : tr::lng_similar_bots_title());
  87. const auto participants = &_peer->session().api().chatParticipants();
  88. Data::AmPremiumValue(
  89. &_peer->session()
  90. ) | rpl::start_with_next([=] {
  91. participants->loadSimilarPeers(_peer);
  92. rebuild();
  93. }, lifetime());
  94. participants->similarLoaded(
  95. ) | rpl::filter(
  96. rpl::mappers::_1 == _peer
  97. ) | rpl::start_with_next([=] {
  98. rebuild();
  99. }, lifetime());
  100. }
  101. void ListController::setContentWidget(not_null<Ui::RpWidget*> widget) {
  102. _content = widget;
  103. }
  104. rpl::producer<int> ListController::unlockHeightValue() const {
  105. return _unlockHeight.value();
  106. }
  107. void ListController::rebuild() {
  108. const auto participants = &_peer->session().api().chatParticipants();
  109. const auto &list = participants->similar(_peer);
  110. for (const auto peer : list.list) {
  111. if (!delegate()->peerListFindRow(peer->id.value)) {
  112. delegate()->peerListAppendRow(createRow(peer));
  113. }
  114. }
  115. if (!list.more
  116. || _peer->session().premium()
  117. || !_peer->session().premiumPossible()) {
  118. delete base::take(_unlock);
  119. _unlockHeight = 0;
  120. } else if (!_unlock) {
  121. setupUnlock();
  122. }
  123. delegate()->peerListRefreshRows();
  124. }
  125. void ListController::setupUnlock() {
  126. Expects(_content != nullptr);
  127. _unlock = Ui::CreateChild<Ui::RpWidget>(_content);
  128. _unlock->show();
  129. const auto button = ::Settings::CreateLockedButton(
  130. _unlock,
  131. (_peer->isBroadcast()
  132. ? tr::lng_similar_channels_show_more()
  133. : tr::lng_similar_bots_show_more()),
  134. st::similarChannelsLock,
  135. rpl::single(true));
  136. button->setClickedCallback([=] {
  137. const auto window = _controller->parentController();
  138. ::Settings::ShowPremium(window, u"similar_channels"_q);
  139. });
  140. const auto upto = Data::PremiumLimits(
  141. &_peer->session()).similarChannelsPremium();
  142. const auto about = Ui::CreateChild<Ui::FlatLabel>(
  143. _unlock,
  144. (_peer->isBroadcast()
  145. ? tr::lng_similar_channels_premium_all
  146. : tr::lng_similar_bots_premium_all)(
  147. lt_count,
  148. rpl::single(upto * 1.),
  149. lt_link,
  150. tr::lng_similar_channels_premium_all_link(
  151. ) | Ui::Text::ToBold() | Ui::Text::ToLink(),
  152. Ui::Text::RichLangValue),
  153. st::similarChannelsLockAbout);
  154. about->setClickHandlerFilter([=](const auto &...) {
  155. const auto window = _controller->parentController();
  156. ::Settings::ShowPremium(window, u"similar_channels"_q);
  157. return false;
  158. });
  159. rpl::combine(
  160. _content->sizeValue(),
  161. (_peer->isBroadcast()
  162. ? tr::lng_similar_channels_show_more()
  163. : tr::lng_similar_bots_show_more())
  164. ) | rpl::start_with_next([=](QSize size, const auto &) {
  165. auto top = st::similarChannelsLockFade
  166. + st::similarChannelsLockPadding.top();
  167. button->setGeometry(
  168. st::similarChannelsLockPadding.left(),
  169. top,
  170. (size.width()
  171. - st::similarChannelsLockPadding.left()
  172. - st::similarChannelsLockPadding.right()),
  173. button->height());
  174. top += button->height() + st::similarChannelsLockPadding.bottom();
  175. const auto minWidth = st::similarChannelsLockAbout.minWidth;
  176. const auto maxWidth = std::max(
  177. minWidth + 1,
  178. (size.width()
  179. - st::similarChannelsLockAboutPadding.left()
  180. - st::similarChannelsLockAboutPadding.right()));
  181. const auto countAboutHeight = [&](int width) {
  182. about->resizeToWidth(width);
  183. return about->height();
  184. };
  185. const auto desired = Ui::FindNiceTooltipWidth(
  186. minWidth,
  187. maxWidth,
  188. countAboutHeight);
  189. about->resizeToWidth(desired);
  190. about->move((size.width() - about->width()) / 2, top);
  191. top += about->height()
  192. + st::similarChannelsLockAboutPadding.bottom();
  193. _unlock->setGeometry(0, size.height() - top, size.width(), top);
  194. }, _unlock->lifetime());
  195. _unlockHeight = _unlock->heightValue();
  196. _unlock->paintRequest(
  197. ) | rpl::start_with_next([=] {
  198. auto p = QPainter(_unlock);
  199. const auto width = _unlock->width();
  200. const auto fade = st::similarChannelsLockFade;
  201. auto gradient = QLinearGradient(0, 0, 0, fade);
  202. gradient.setStops({
  203. { 0., QColor(255, 255, 255, 0) },
  204. { 1., st::windowBg->c },
  205. });
  206. p.fillRect(0, 0, width, fade, gradient);
  207. p.fillRect(0, fade, width, _unlock->height() - fade, st::windowBg);
  208. }, _unlock->lifetime());
  209. }
  210. void ListController::loadMoreRows() {
  211. }
  212. std::unique_ptr<PeerListState> ListController::saveState() const {
  213. auto result = PeerListController::saveState();
  214. auto my = std::make_unique<SavedState>();
  215. result->controllerState = std::move(my);
  216. return result;
  217. }
  218. void ListController::restoreState(
  219. std::unique_ptr<PeerListState> state) {
  220. auto typeErasedState = state
  221. ? state->controllerState.get()
  222. : nullptr;
  223. if (dynamic_cast<SavedState*>(typeErasedState)) {
  224. PeerListController::restoreState(std::move(state));
  225. }
  226. }
  227. void ListController::rowClicked(not_null<PeerListRow*> row) {
  228. _controller->parentController()->showPeerHistory(
  229. row->peer(),
  230. Window::SectionShow::Way::Forward);
  231. }
  232. } // namespace
  233. class InnerWidget final
  234. : public Ui::RpWidget
  235. , private PeerListContentDelegate {
  236. public:
  237. InnerWidget(
  238. QWidget *parent,
  239. not_null<Controller*> controller,
  240. not_null<PeerData*> peer);
  241. [[nodiscard]] not_null<PeerData*> peer() const {
  242. return _peer;
  243. }
  244. rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
  245. int desiredHeight() const;
  246. void saveState(not_null<Memento*> memento);
  247. void restoreState(not_null<Memento*> memento);
  248. protected:
  249. void visibleTopBottomUpdated(
  250. int visibleTop,
  251. int visibleBottom) override;
  252. private:
  253. using ListWidget = PeerListContent;
  254. // PeerListContentDelegate interface.
  255. void peerListSetTitle(rpl::producer<QString> title) override;
  256. void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
  257. bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
  258. int peerListSelectedRowsCount() override;
  259. void peerListScrollToTop() override;
  260. void peerListAddSelectedPeerInBunch(
  261. not_null<PeerData*> peer) override;
  262. void peerListAddSelectedRowInBunch(
  263. not_null<PeerListRow*> row) override;
  264. void peerListFinishSelectedRowsBunch() override;
  265. void peerListSetDescription(
  266. object_ptr<Ui::FlatLabel> description) override;
  267. std::shared_ptr<Main::SessionShow> peerListUiShow() override;
  268. object_ptr<ListWidget> setupList(
  269. RpWidget *parent,
  270. not_null<ListController*> controller);
  271. const std::shared_ptr<Main::SessionShow> _show;
  272. not_null<Controller*> _controller;
  273. const not_null<PeerData*> _peer;
  274. std::unique_ptr<ListController> _listController;
  275. object_ptr<ListWidget> _list;
  276. rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
  277. };
  278. InnerWidget::InnerWidget(
  279. QWidget *parent,
  280. not_null<Controller*> controller,
  281. not_null<PeerData*> peer)
  282. : RpWidget(parent)
  283. , _show(controller->uiShow())
  284. , _controller(controller)
  285. , _peer(peer)
  286. , _listController(std::make_unique<ListController>(controller, _peer))
  287. , _list(setupList(this, _listController.get())) {
  288. setContent(_list.data());
  289. _listController->setDelegate(static_cast<PeerListDelegate*>(this));
  290. }
  291. void InnerWidget::visibleTopBottomUpdated(
  292. int visibleTop,
  293. int visibleBottom) {
  294. setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
  295. }
  296. void InnerWidget::saveState(not_null<Memento*> memento) {
  297. memento->setListState(_listController->saveState());
  298. }
  299. void InnerWidget::restoreState(not_null<Memento*> memento) {
  300. _listController->restoreState(memento->listState());
  301. }
  302. rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
  303. return _scrollToRequests.events();
  304. }
  305. int InnerWidget::desiredHeight() const {
  306. auto desired = 0;
  307. desired += _list->fullRowsCount() * st::infoMembersList.item.height;
  308. return qMax(height(), desired);
  309. }
  310. object_ptr<InnerWidget::ListWidget> InnerWidget::setupList(
  311. RpWidget *parent,
  312. not_null<ListController*> controller) {
  313. controller->setStyleOverrides(&st::infoMembersList);
  314. auto result = object_ptr<ListWidget>(
  315. parent,
  316. controller);
  317. controller->setContentWidget(this);
  318. result->scrollToRequests(
  319. ) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
  320. auto addmin = (request.ymin < 0)
  321. ? 0
  322. : st::infoCommonGroupsMargin.top();
  323. auto addmax = (request.ymax < 0)
  324. ? 0
  325. : st::infoCommonGroupsMargin.top();
  326. _scrollToRequests.fire({
  327. request.ymin + addmin,
  328. request.ymax + addmax });
  329. }, result->lifetime());
  330. result->moveToLeft(0, st::infoCommonGroupsMargin.top());
  331. parent->widthValue(
  332. ) | rpl::start_with_next([list = result.data()](int newWidth) {
  333. list->resizeToWidth(newWidth);
  334. }, result->lifetime());
  335. rpl::combine(
  336. result->heightValue(),
  337. controller->unlockHeightValue()
  338. ) | rpl::start_with_next([=](int listHeight, int unlockHeight) {
  339. auto newHeight = st::infoCommonGroupsMargin.top()
  340. + listHeight
  341. + (unlockHeight
  342. ? (unlockHeight - st::similarChannelsLockOverlap)
  343. : st::infoCommonGroupsMargin.bottom());
  344. parent->resize(parent->width(), std::max(newHeight, 0));
  345. }, result->lifetime());
  346. return result;
  347. }
  348. void InnerWidget::peerListSetTitle(rpl::producer<QString> title) {
  349. }
  350. void InnerWidget::peerListSetAdditionalTitle(rpl::producer<QString> title) {
  351. }
  352. bool InnerWidget::peerListIsRowChecked(not_null<PeerListRow*> row) {
  353. return false;
  354. }
  355. int InnerWidget::peerListSelectedRowsCount() {
  356. return 0;
  357. }
  358. void InnerWidget::peerListScrollToTop() {
  359. _scrollToRequests.fire({ -1, -1 });
  360. }
  361. void InnerWidget::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
  362. Unexpected("Item selection in Info::Profile::Members.");
  363. }
  364. void InnerWidget::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
  365. Unexpected("Item selection in Info::Profile::Members.");
  366. }
  367. void InnerWidget::peerListFinishSelectedRowsBunch() {
  368. }
  369. void InnerWidget::peerListSetDescription(
  370. object_ptr<Ui::FlatLabel> description) {
  371. description.destroy();
  372. }
  373. std::shared_ptr<Main::SessionShow> InnerWidget::peerListUiShow() {
  374. return _show;
  375. }
  376. Memento::Memento(not_null<PeerData*> peer)
  377. : ContentMemento(peer, nullptr, PeerId()) {
  378. }
  379. Section Memento::section() const {
  380. return Section(Section::Type::SimilarPeers);
  381. }
  382. object_ptr<ContentWidget> Memento::createWidget(
  383. QWidget *parent,
  384. not_null<Controller*> controller,
  385. const QRect &geometry) {
  386. auto result = object_ptr<Widget>(parent, controller, peer());
  387. result->setInternalState(geometry, this);
  388. return result;
  389. }
  390. void Memento::setListState(std::unique_ptr<PeerListState> state) {
  391. _listState = std::move(state);
  392. }
  393. std::unique_ptr<PeerListState> Memento::listState() {
  394. return std::move(_listState);
  395. }
  396. Memento::~Memento() = default;
  397. Widget::Widget(
  398. QWidget *parent,
  399. not_null<Controller*> controller,
  400. not_null<PeerData*> peer)
  401. : ContentWidget(parent, controller) {
  402. _inner = setInnerWidget(object_ptr<InnerWidget>(
  403. this,
  404. controller,
  405. peer));
  406. }
  407. rpl::producer<QString> Widget::title() {
  408. return peer()->isBroadcast()
  409. ? tr::lng_similar_channels_title()
  410. : tr::lng_similar_bots_title();
  411. }
  412. not_null<PeerData*> Widget::peer() const {
  413. return _inner->peer();
  414. }
  415. bool Widget::showInternal(not_null<ContentMemento*> memento) {
  416. if (!controller()->validateMementoPeer(memento)) {
  417. return false;
  418. }
  419. if (auto similarMemento = dynamic_cast<Memento*>(memento.get())) {
  420. if (similarMemento->peer() == peer()) {
  421. restoreState(similarMemento);
  422. return true;
  423. }
  424. }
  425. return false;
  426. }
  427. void Widget::setInternalState(
  428. const QRect &geometry,
  429. not_null<Memento*> memento) {
  430. setGeometry(geometry);
  431. Ui::SendPendingMoveResizeEvents(this);
  432. restoreState(memento);
  433. }
  434. std::shared_ptr<ContentMemento> Widget::doCreateMemento() {
  435. auto result = std::make_shared<Memento>(peer());
  436. saveState(result.get());
  437. return result;
  438. }
  439. void Widget::saveState(not_null<Memento*> memento) {
  440. memento->setScrollTop(scrollTopSave());
  441. _inner->saveState(memento);
  442. }
  443. void Widget::restoreState(not_null<Memento*> memento) {
  444. _inner->restoreState(memento);
  445. scrollTopRestore(memento->scrollTop());
  446. }
  447. } // namespace Info::SimilarPeers