settings_websites.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  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 "settings/settings_websites.h"
  8. #include "api/api_websites.h"
  9. #include "apiwrap.h"
  10. #include "boxes/peer_list_box.h"
  11. #include "data/data_user.h"
  12. #include "ui/boxes/confirm_box.h"
  13. #include "lang/lang_keys.h"
  14. #include "main/main_session.h"
  15. #include "settings/settings_active_sessions.h"
  16. #include "ui/controls/userpic_button.h"
  17. #include "ui/widgets/checkbox.h"
  18. #include "ui/wrap/slide_wrap.h"
  19. #include "ui/wrap/padding_wrap.h"
  20. #include "ui/wrap/vertical_layout.h"
  21. #include "ui/layers/generic_box.h"
  22. #include "ui/painter.h"
  23. #include "ui/vertical_list.h"
  24. #include "window/window_session_controller.h"
  25. #include "styles/style_info.h"
  26. #include "styles/style_layers.h"
  27. #include "styles/style_settings.h"
  28. #include "styles/style_menu_icons.h"
  29. namespace {
  30. constexpr auto kShortPollTimeout = 60 * crl::time(1000);
  31. using EntryData = Api::Websites::Entry;
  32. class Row;
  33. class RowDelegate {
  34. public:
  35. virtual void rowUpdateRow(not_null<Row*> row) = 0;
  36. };
  37. class Row final : public PeerListRow {
  38. public:
  39. Row(not_null<RowDelegate*> delegate, const EntryData &data);
  40. void update(const EntryData &data);
  41. void updateName(const QString &name);
  42. [[nodiscard]] EntryData data() const;
  43. QString generateName() override;
  44. QString generateShortName() override;
  45. PaintRoundImageCallback generatePaintUserpicCallback(
  46. bool forceRound) override;
  47. QSize rightActionSize() const override {
  48. return elementGeometry(2, 0).size();
  49. }
  50. QMargins rightActionMargins() const override {
  51. const auto rect = elementGeometry(2, 0);
  52. return QMargins(0, rect.y(), -(rect.x() + rect.width()), 0);
  53. }
  54. int elementsCount() const override;
  55. QRect elementGeometry(int element, int outerWidth) const override;
  56. bool elementDisabled(int element) const override;
  57. bool elementOnlySelect(int element) const override;
  58. void elementAddRipple(
  59. int element,
  60. QPoint point,
  61. Fn<void()> updateCallback) override;
  62. void elementsStopLastRipple() override;
  63. void elementsPaint(
  64. Painter &p,
  65. int outerWidth,
  66. bool selected,
  67. int selectedElement) override;
  68. private:
  69. const not_null<RowDelegate*> _delegate;
  70. QImage _emptyUserpic;
  71. Ui::PeerUserpicView _userpic;
  72. Ui::Text::String _location;
  73. EntryData _data;
  74. };
  75. [[nodiscard]] QString JoinNonEmpty(QStringList list) {
  76. list.erase(ranges::remove(list, QString()), list.end());
  77. return list.join(", ");
  78. }
  79. [[nodiscard]] QString LocationAndDate(const EntryData &entry) {
  80. return (entry.location.isEmpty() ? entry.ip : entry.location)
  81. + (entry.hash
  82. ? (QString::fromUtf8(" \xE2\x80\xA2 ") + entry.active)
  83. : QString());
  84. }
  85. void InfoBox(
  86. not_null<Ui::GenericBox*> box,
  87. const EntryData &data,
  88. Fn<void(uint64)> terminate) {
  89. box->setWidth(st::boxWideWidth);
  90. const auto shown = box->lifetime().make_state<rpl::event_stream<>>();
  91. box->setShowFinishedCallback([=] {
  92. shown->fire({});
  93. });
  94. const auto userpic = box->addRow(
  95. object_ptr<Ui::CenterWrap<Ui::UserpicButton>>(
  96. box,
  97. object_ptr<Ui::UserpicButton>(
  98. box,
  99. data.bot,
  100. st::websiteBigUserpic)),
  101. st::sessionBigCoverPadding)->entity();
  102. userpic->forceForumShape(true);
  103. userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
  104. const auto nameWrap = box->addRow(
  105. object_ptr<Ui::FixedHeightWidget>(
  106. box,
  107. st::sessionBigName.maxHeight));
  108. const auto name = Ui::CreateChild<Ui::FlatLabel>(
  109. nameWrap,
  110. rpl::single(data.bot->name()),
  111. st::sessionBigName);
  112. nameWrap->widthValue(
  113. ) | rpl::start_with_next([=](int width) {
  114. name->resizeToWidth(width);
  115. name->move((width - name->width()) / 2, 0);
  116. }, name->lifetime());
  117. const auto domainWrap = box->addRow(
  118. object_ptr<Ui::FixedHeightWidget>(
  119. box,
  120. st::sessionDateLabel.style.font->height),
  121. style::margins(0, 0, 0, st::sessionDateSkip));
  122. const auto domain = Ui::CreateChild<Ui::FlatLabel>(
  123. domainWrap,
  124. rpl::single(data.domain),
  125. st::sessionDateLabel);
  126. rpl::combine(
  127. domainWrap->widthValue(),
  128. domain->widthValue()
  129. ) | rpl::start_with_next([=](int outer, int inner) {
  130. domain->move((outer - inner) / 2, 0);
  131. }, domain->lifetime());
  132. using namespace Settings;
  133. const auto container = box->verticalLayout();
  134. Ui::AddDivider(container);
  135. Ui::AddSkip(container, st::sessionSubtitleSkip);
  136. Ui::AddSubsectionTitle(container, tr::lng_sessions_info());
  137. AddSessionInfoRow(
  138. container,
  139. tr::lng_sessions_browser(),
  140. JoinNonEmpty({ data.browser, data.platform }),
  141. st::menuIconDevices);
  142. AddSessionInfoRow(
  143. container,
  144. tr::lng_sessions_ip(),
  145. data.ip,
  146. st::menuIconIpAddress);
  147. AddSessionInfoRow(
  148. container,
  149. tr::lng_sessions_location(),
  150. data.location,
  151. st::menuIconAddress);
  152. Ui::AddSkip(container, st::sessionValueSkip);
  153. if (!data.location.isEmpty()) {
  154. Ui::AddDividerText(container, tr::lng_sessions_location_about());
  155. }
  156. box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
  157. if (const auto hash = data.hash) {
  158. box->addLeftButton(tr::lng_settings_disconnect(), [=] {
  159. const auto weak = Ui::MakeWeak(box.get());
  160. terminate(hash);
  161. if (weak) {
  162. box->closeBox();
  163. }
  164. }, st::attentionBoxButton);
  165. }
  166. }
  167. Row::Row(not_null<RowDelegate*> delegate, const EntryData &data)
  168. : PeerListRow(data.hash)
  169. , _delegate(delegate)
  170. , _location(st::defaultTextStyle, LocationAndDate(data))
  171. , _data(data) {
  172. setCustomStatus(_data.ip);
  173. }
  174. void Row::update(const EntryData &data) {
  175. _data = data;
  176. setCustomStatus(
  177. JoinNonEmpty({ _data.domain, _data.browser, _data.platform }));
  178. refreshName(st::websiteListItem);
  179. _location.setText(st::defaultTextStyle, LocationAndDate(_data));
  180. _delegate->rowUpdateRow(this);
  181. }
  182. EntryData Row::data() const {
  183. return _data;
  184. }
  185. QString Row::generateName() {
  186. return _data.bot->name();
  187. }
  188. QString Row::generateShortName() {
  189. return _data.bot->shortName();
  190. }
  191. PaintRoundImageCallback Row::generatePaintUserpicCallback(bool forceRound) {
  192. const auto peer = _data.bot;
  193. auto userpic = _userpic = peer->createUserpicView();
  194. return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
  195. const auto ratio = style::DevicePixelRatio();
  196. if (const auto cloud = peer->userpicCloudImage(userpic)) {
  197. Ui::ValidateUserpicCache(
  198. userpic,
  199. cloud,
  200. nullptr,
  201. size * ratio,
  202. true);
  203. p.drawImage(QRect(x, y, size, size), userpic.cached);
  204. } else {
  205. if (_emptyUserpic.isNull()) {
  206. _emptyUserpic = PeerData::GenerateUserpicImage(
  207. peer,
  208. _userpic,
  209. size * ratio,
  210. size * ratio * Ui::ForumUserpicRadiusMultiplier());
  211. }
  212. p.drawImage(QRect(x, y, size, size), _emptyUserpic);
  213. }
  214. };
  215. }
  216. int Row::elementsCount() const {
  217. return 2;
  218. }
  219. QRect Row::elementGeometry(int element, int outerWidth) const {
  220. switch (element) {
  221. case 1: {
  222. return QRect(
  223. st::websiteListItem.namePosition.x(),
  224. st::websiteLocationTop,
  225. outerWidth,
  226. st::normalFont->height);
  227. } break;
  228. case 2: {
  229. const auto size = QSize(
  230. st::sessionTerminate.width,
  231. st::sessionTerminate.height);
  232. const auto right = st::sessionTerminateSkip;
  233. const auto top = st::sessionTerminateTop;
  234. const auto left = outerWidth - right - size.width();
  235. return QRect(QPoint(left, top), size);
  236. } break;
  237. }
  238. return QRect();
  239. }
  240. bool Row::elementDisabled(int element) const {
  241. return !id() || (element == 1);
  242. }
  243. bool Row::elementOnlySelect(int element) const {
  244. return false;
  245. }
  246. void Row::elementAddRipple(
  247. int element,
  248. QPoint point,
  249. Fn<void()> updateCallback) {
  250. }
  251. void Row::elementsStopLastRipple() {
  252. }
  253. void Row::elementsPaint(
  254. Painter &p,
  255. int outerWidth,
  256. bool selected,
  257. int selectedElement) {
  258. const auto geometry = elementGeometry(2, outerWidth);
  259. const auto position = geometry.topLeft()
  260. + st::sessionTerminate.iconPosition;
  261. const auto &icon = (selectedElement == 2)
  262. ? st::sessionTerminate.iconOver
  263. : st::sessionTerminate.icon;
  264. icon.paint(p, position.x(), position.y(), outerWidth);
  265. p.setFont(st::normalFont);
  266. p.setPen(st::sessionInfoFg);
  267. const auto locationLeft = st::websiteListItem.namePosition.x();
  268. const auto available = outerWidth - locationLeft;
  269. _location.drawLeftElided(
  270. p,
  271. locationLeft,
  272. st::websiteLocationTop,
  273. available,
  274. outerWidth);
  275. }
  276. class Content : public Ui::RpWidget {
  277. public:
  278. Content(
  279. QWidget*,
  280. not_null<Window::SessionController*> controller);
  281. void setupContent();
  282. protected:
  283. void resizeEvent(QResizeEvent *e) override;
  284. void paintEvent(QPaintEvent *e) override;
  285. private:
  286. class Inner;
  287. class ListController;
  288. void shortPoll();
  289. void parse(const Api::Websites::List &list);
  290. void terminate(
  291. Fn<void(bool block)> sendRequest,
  292. rpl::producer<QString> title,
  293. rpl::producer<QString> text,
  294. QString blockText = QString());
  295. void terminateOne(uint64 hash);
  296. void terminateAll();
  297. const not_null<Window::SessionController*> _controller;
  298. const not_null<Api::Websites*> _websites;
  299. rpl::variable<bool> _loading = false;
  300. Api::Websites::List _data;
  301. object_ptr<Inner> _inner;
  302. QPointer<Ui::BoxContent> _terminateBox;
  303. base::Timer _shortPollTimer;
  304. };
  305. class Content::ListController final
  306. : public PeerListController
  307. , public RowDelegate
  308. , public base::has_weak_ptr {
  309. public:
  310. explicit ListController(not_null<Main::Session*> session);
  311. Main::Session &session() const override;
  312. void prepare() override;
  313. void rowClicked(not_null<PeerListRow*> row) override;
  314. void rowElementClicked(not_null<PeerListRow*> row, int element) override;
  315. void rowUpdateRow(not_null<Row*> row) override;
  316. void showData(gsl::span<const EntryData> items);
  317. rpl::producer<int> itemsCount() const;
  318. rpl::producer<uint64> terminateRequests() const;
  319. [[nodiscard]] rpl::producer<EntryData> showRequests() const;
  320. [[nodiscard]] static std::unique_ptr<ListController> Add(
  321. not_null<Ui::VerticalLayout*> container,
  322. not_null<Main::Session*> session,
  323. style::margins margins = {});
  324. private:
  325. const not_null<Main::Session*> _session;
  326. rpl::event_stream<uint64> _terminateRequests;
  327. rpl::event_stream<int> _itemsCount;
  328. rpl::event_stream<EntryData> _showRequests;
  329. };
  330. class Content::Inner : public Ui::RpWidget {
  331. public:
  332. Inner(
  333. QWidget *parent,
  334. not_null<Window::SessionController*> controller);
  335. void showData(const Api::Websites::List &data);
  336. [[nodiscard]] rpl::producer<EntryData> showRequests() const;
  337. [[nodiscard]] rpl::producer<uint64> terminateOne() const;
  338. [[nodiscard]] rpl::producer<> terminateAll() const;
  339. private:
  340. void setupContent();
  341. const not_null<Window::SessionController*> _controller;
  342. QPointer<Ui::SettingsButton> _terminateAll;
  343. std::unique_ptr<ListController> _list;
  344. };
  345. Content::Content(
  346. QWidget*,
  347. not_null<Window::SessionController*> controller)
  348. : _controller(controller)
  349. , _websites(&controller->session().api().websites())
  350. , _inner(this, controller)
  351. , _shortPollTimer([=] { shortPoll(); }) {
  352. }
  353. void Content::setupContent() {
  354. _inner->heightValue(
  355. ) | rpl::distinct_until_changed(
  356. ) | rpl::start_with_next([=](int height) {
  357. resize(width(), height);
  358. }, _inner->lifetime());
  359. _inner->showRequests(
  360. ) | rpl::start_with_next([=](const EntryData &data) {
  361. _controller->show(Box(
  362. InfoBox,
  363. data,
  364. [=](uint64 hash) { terminateOne(hash); }));
  365. }, lifetime());
  366. _inner->terminateOne(
  367. ) | rpl::start_with_next([=](uint64 hash) {
  368. terminateOne(hash);
  369. }, lifetime());
  370. _inner->terminateAll(
  371. ) | rpl::start_with_next([=] {
  372. terminateAll();
  373. }, lifetime());
  374. _loading.changes(
  375. ) | rpl::start_with_next([=](bool value) {
  376. _inner->setVisible(!value);
  377. }, lifetime());
  378. _websites->listValue(
  379. ) | rpl::start_with_next([=](const Api::Websites::List &list) {
  380. parse(list);
  381. }, lifetime());
  382. _loading = true;
  383. shortPoll();
  384. }
  385. void Content::parse(const Api::Websites::List &list) {
  386. _loading = false;
  387. _data = list;
  388. ranges::sort(_data, std::greater<>(), &EntryData::activeTime);
  389. _inner->showData(_data);
  390. _shortPollTimer.callOnce(kShortPollTimeout);
  391. }
  392. void Content::resizeEvent(QResizeEvent *e) {
  393. RpWidget::resizeEvent(e);
  394. _inner->resize(width(), _inner->height());
  395. }
  396. void Content::paintEvent(QPaintEvent *e) {
  397. RpWidget::paintEvent(e);
  398. Painter p(this);
  399. if (_loading.current()) {
  400. p.setFont(st::noContactsFont);
  401. p.setPen(st::noContactsColor);
  402. p.drawText(
  403. QRect(0, 0, width(), st::noContactsHeight),
  404. tr::lng_contacts_loading(tr::now),
  405. style::al_center);
  406. }
  407. }
  408. void Content::shortPoll() {
  409. const auto left = kShortPollTimeout
  410. - (crl::now() - _websites->lastReceivedTime());
  411. if (left > 0) {
  412. parse(_websites->list());
  413. _shortPollTimer.cancel();
  414. _shortPollTimer.callOnce(left);
  415. } else {
  416. _websites->reload();
  417. }
  418. update();
  419. }
  420. void Content::terminate(
  421. Fn<void(bool block)> sendRequest,
  422. rpl::producer<QString> title,
  423. rpl::producer<QString> text,
  424. QString blockText) {
  425. if (const auto strong = _terminateBox.data()) {
  426. strong->deleteLater();
  427. }
  428. auto box = Box([=](not_null<Ui::GenericBox*> box) {
  429. auto &lifetime = box->lifetime();
  430. const auto block = lifetime.make_state<Ui::Checkbox*>(nullptr);
  431. const auto callback = crl::guard(this, [=] {
  432. const auto blocked = (*block) && (*block)->checked();
  433. if (_terminateBox) {
  434. _terminateBox->closeBox();
  435. _terminateBox = nullptr;
  436. }
  437. sendRequest(blocked);
  438. });
  439. Ui::ConfirmBox(box, {
  440. .text = rpl::duplicate(text),
  441. .confirmed = callback,
  442. .confirmText = tr::lng_settings_disconnect(),
  443. .confirmStyle = &st::attentionBoxButton,
  444. .title = rpl::duplicate(title),
  445. });
  446. if (!blockText.isEmpty()) {
  447. *block = box->addRow(object_ptr<Ui::Checkbox>(box, blockText));
  448. }
  449. });
  450. _terminateBox = Ui::MakeWeak(box.data());
  451. _controller->show(std::move(box));
  452. }
  453. void Content::terminateOne(uint64 hash) {
  454. const auto weak = Ui::MakeWeak(this);
  455. const auto i = ranges::find(_data, hash, &EntryData::hash);
  456. if (i == end(_data)) {
  457. return;
  458. }
  459. const auto bot = i->bot;
  460. auto callback = [=](bool block) {
  461. auto done = crl::guard(weak, [=](const MTPBool &result) {
  462. _data.erase(
  463. ranges::remove(_data, hash, &EntryData::hash),
  464. end(_data));
  465. _inner->showData(_data);
  466. });
  467. auto fail = crl::guard(weak, [=](const MTP::Error &error) {
  468. });
  469. _websites->requestTerminate(
  470. std::move(done),
  471. std::move(fail),
  472. hash,
  473. block ? bot.get() : nullptr);
  474. };
  475. terminate(
  476. std::move(callback),
  477. tr::lng_settings_disconnect_title(),
  478. tr::lng_settings_disconnect_sure(lt_domain, rpl::single(i->domain)),
  479. tr::lng_settings_disconnect_block(tr::now, lt_name, bot->name()));
  480. }
  481. void Content::terminateAll() {
  482. const auto weak = Ui::MakeWeak(this);
  483. auto callback = [=](bool block) {
  484. const auto reset = crl::guard(weak, [=] {
  485. _websites->cancelCurrentRequest();
  486. _websites->reload();
  487. });
  488. _websites->requestTerminate(
  489. [=](const MTPBool &result) { reset(); },
  490. [=](const MTP::Error &result) { reset(); });
  491. _loading = true;
  492. };
  493. terminate(
  494. std::move(callback),
  495. tr::lng_settings_disconnect_all_title(),
  496. tr::lng_settings_disconnect_all_sure());
  497. }
  498. Content::Inner::Inner(
  499. QWidget *parent,
  500. not_null<Window::SessionController*> controller)
  501. : RpWidget(parent)
  502. , _controller(controller) {
  503. resize(width(), st::noContactsHeight);
  504. setupContent();
  505. }
  506. void Content::Inner::setupContent() {
  507. using namespace Settings;
  508. using namespace rpl::mappers;
  509. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  510. const auto session = &_controller->session();
  511. const auto terminateWrap = content->add(
  512. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  513. content,
  514. object_ptr<Ui::VerticalLayout>(content)))->setDuration(0);
  515. const auto terminateInner = terminateWrap->entity();
  516. _terminateAll = terminateInner->add(
  517. CreateButtonWithIcon(
  518. terminateInner,
  519. tr::lng_settings_disconnect_all(),
  520. st::infoBlockButton,
  521. { .icon = &st::infoIconBlock }));
  522. Ui::AddSkip(terminateInner);
  523. Ui::AddDividerText(
  524. terminateInner,
  525. tr::lng_settings_logged_in_description());
  526. const auto listWrap = content->add(
  527. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  528. content,
  529. object_ptr<Ui::VerticalLayout>(content)))->setDuration(0);
  530. const auto listInner = listWrap->entity();
  531. Ui::AddSkip(listInner, st::sessionSubtitleSkip);
  532. Ui::AddSubsectionTitle(listInner, tr::lng_settings_logged_in_title());
  533. _list = ListController::Add(listInner, session);
  534. Ui::AddSkip(listInner);
  535. const auto skip = st::noContactsHeight / 2;
  536. const auto placeholder = content->add(
  537. object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
  538. content,
  539. object_ptr<Ui::FlatLabel>(
  540. content,
  541. tr::lng_settings_logged_in_description(),
  542. st::boxDividerLabel),
  543. st::defaultBoxDividerLabelPadding + QMargins(0, skip, 0, skip))
  544. )->setDuration(0);
  545. terminateWrap->toggleOn(_list->itemsCount() | rpl::map(_1 > 0));
  546. listWrap->toggleOn(_list->itemsCount() | rpl::map(_1 > 0));
  547. placeholder->toggleOn(_list->itemsCount() | rpl::map(_1 == 0));
  548. Ui::ResizeFitChild(this, content);
  549. }
  550. void Content::Inner::showData(const Api::Websites::List &data) {
  551. _list->showData(data);
  552. }
  553. rpl::producer<> Content::Inner::terminateAll() const {
  554. return _terminateAll->clicks() | rpl::to_empty;
  555. }
  556. rpl::producer<uint64> Content::Inner::terminateOne() const {
  557. return _list->terminateRequests();
  558. }
  559. rpl::producer<EntryData> Content::Inner::showRequests() const {
  560. return _list->showRequests();
  561. }
  562. Content::ListController::ListController(
  563. not_null<Main::Session*> session)
  564. : _session(session) {
  565. }
  566. Main::Session &Content::ListController::session() const {
  567. return *_session;
  568. }
  569. void Content::ListController::prepare() {
  570. }
  571. void Content::ListController::rowClicked(
  572. not_null<PeerListRow*> row) {
  573. _showRequests.fire_copy(static_cast<Row*>(row.get())->data());
  574. }
  575. void Content::ListController::rowElementClicked(
  576. not_null<PeerListRow*> row,
  577. int element) {
  578. if (element == 2) {
  579. if (const auto hash = static_cast<Row*>(row.get())->data().hash) {
  580. _terminateRequests.fire_copy(hash);
  581. }
  582. }
  583. }
  584. void Content::ListController::rowUpdateRow(not_null<Row*> row) {
  585. delegate()->peerListUpdateRow(row);
  586. }
  587. void Content::ListController::showData(
  588. gsl::span<const EntryData> items) {
  589. auto index = 0;
  590. auto positions = base::flat_map<uint64, int>();
  591. positions.reserve(items.size());
  592. for (const auto &entry : items) {
  593. const auto id = entry.hash;
  594. positions.emplace(id, index++);
  595. if (const auto row = delegate()->peerListFindRow(id)) {
  596. static_cast<Row*>(row)->update(entry);
  597. } else {
  598. delegate()->peerListAppendRow(
  599. std::make_unique<Row>(this, entry));
  600. }
  601. }
  602. for (auto i = 0; i != delegate()->peerListFullRowsCount();) {
  603. const auto row = delegate()->peerListRowAt(i);
  604. if (positions.contains(row->id())) {
  605. ++i;
  606. continue;
  607. }
  608. delegate()->peerListRemoveRow(row);
  609. }
  610. delegate()->peerListSortRows([&](
  611. const PeerListRow &a,
  612. const PeerListRow &b) {
  613. return positions[a.id()] < positions[b.id()];
  614. });
  615. delegate()->peerListRefreshRows();
  616. _itemsCount.fire(delegate()->peerListFullRowsCount());
  617. }
  618. rpl::producer<int> Content::ListController::itemsCount() const {
  619. return _itemsCount.events_starting_with(
  620. delegate()->peerListFullRowsCount());
  621. }
  622. rpl::producer<uint64> Content::ListController::terminateRequests() const {
  623. return _terminateRequests.events();
  624. }
  625. rpl::producer<EntryData> Content::ListController::showRequests() const {
  626. return _showRequests.events();
  627. }
  628. auto Content::ListController::Add(
  629. not_null<Ui::VerticalLayout*> container,
  630. not_null<Main::Session*> session,
  631. style::margins margins)
  632. -> std::unique_ptr<ListController> {
  633. auto &lifetime = container->lifetime();
  634. const auto delegate = lifetime.make_state<
  635. PeerListContentDelegateSimple
  636. >();
  637. auto controller = std::make_unique<ListController>(session);
  638. controller->setStyleOverrides(&st::websiteList);
  639. const auto content = container->add(
  640. object_ptr<PeerListContent>(
  641. container,
  642. controller.get()),
  643. margins);
  644. delegate->setContent(content);
  645. controller->setDelegate(delegate);
  646. return controller;
  647. }
  648. } // namespace
  649. namespace Settings {
  650. Websites::Websites(
  651. QWidget *parent,
  652. not_null<Window::SessionController*> controller)
  653. : Section(parent) {
  654. setupContent(controller);
  655. }
  656. rpl::producer<QString> Websites::title() {
  657. return tr::lng_settings_connected_title();
  658. }
  659. void Websites::setupContent(not_null<Window::SessionController*> controller) {
  660. const auto container = Ui::CreateChild<Ui::VerticalLayout>(this);
  661. Ui::AddSkip(container);
  662. const auto content = container->add(
  663. object_ptr<Content>(container, controller));
  664. content->setupContent();
  665. Ui::ResizeFitChild(this, container);
  666. }
  667. } // namespace Settings