info_peer_gifts_widget.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  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/peer_gifts/info_peer_gifts_widget.h"
  8. #include "api/api_premium.h"
  9. #include "apiwrap.h"
  10. #include "data/data_channel.h"
  11. #include "data/data_credits.h"
  12. #include "data/data_session.h"
  13. #include "data/data_user.h"
  14. #include "info/peer_gifts/info_peer_gifts_common.h"
  15. #include "info/info_controller.h"
  16. #include "ui/layers/generic_box.h"
  17. #include "ui/text/text_utilities.h"
  18. #include "ui/widgets/menu/menu_add_action_callback.h"
  19. #include "ui/widgets/box_content_divider.h"
  20. #include "ui/widgets/checkbox.h"
  21. #include "ui/widgets/labels.h"
  22. #include "ui/widgets/popup_menu.h"
  23. #include "ui/widgets/scroll_area.h"
  24. #include "ui/wrap/slide_wrap.h"
  25. #include "ui/ui_utility.h"
  26. #include "lang/lang_keys.h"
  27. #include "main/main_app_config.h"
  28. #include "main/main_session.h"
  29. #include "mtproto/sender.h"
  30. #include "window/window_session_controller.h"
  31. #include "settings/settings_credits_graphics.h"
  32. #include "styles/style_info.h"
  33. #include "styles/style_layers.h" // boxRadius
  34. #include "styles/style_media_player.h" // mediaPlayerMenuCheck
  35. #include "styles/style_menu_icons.h"
  36. #include "styles/style_credits.h" // giftBoxPadding
  37. namespace Info::PeerGifts {
  38. namespace {
  39. constexpr auto kPreloadPages = 2;
  40. constexpr auto kPerPage = 50;
  41. [[nodiscard]] GiftDescriptor DescriptorForGift(
  42. not_null<PeerData*> to,
  43. const Data::SavedStarGift &gift) {
  44. return GiftTypeStars{
  45. .info = gift.info,
  46. .from = ((gift.anonymous || !gift.fromId)
  47. ? nullptr
  48. : to->owner().peer(gift.fromId).get()),
  49. .date = gift.date,
  50. .userpic = !gift.info.unique,
  51. .pinned = gift.pinned,
  52. .hidden = gift.hidden,
  53. .mine = to->isSelf(),
  54. };
  55. }
  56. } // namespace
  57. class InnerWidget final : public Ui::BoxContentDivider {
  58. public:
  59. InnerWidget(
  60. QWidget *parent,
  61. not_null<Controller*> controller,
  62. not_null<PeerData*> peer,
  63. rpl::producer<Filter> filter);
  64. [[nodiscard]] not_null<PeerData*> peer() const {
  65. return _peer;
  66. }
  67. [[nodiscard]] rpl::producer<bool> notifyEnabled() const {
  68. return _notifyEnabled.events();
  69. }
  70. [[nodiscard]] rpl::producer<> scrollToTop() const {
  71. return _scrollToTop.events();
  72. }
  73. void saveState(not_null<Memento*> memento);
  74. void restoreState(not_null<Memento*> memento);
  75. private:
  76. struct Entry {
  77. Data::SavedStarGift gift;
  78. GiftDescriptor descriptor;
  79. };
  80. struct View {
  81. std::unique_ptr<GiftButton> button;
  82. Data::SavedStarGiftId manageId;
  83. uint64 giftId = 0;
  84. int index = 0;
  85. };
  86. void visibleTopBottomUpdated(
  87. int visibleTop,
  88. int visibleBottom) override;
  89. void paintEvent(QPaintEvent *e) override;
  90. void subscribeToUpdates();
  91. void loadMore();
  92. void refreshButtons();
  93. void validateButtons();
  94. void showGift(int index);
  95. void showMenuFor(not_null<GiftButton*> button, QPoint point);
  96. void refreshAbout();
  97. void markPinned(std::vector<Entry>::iterator i);
  98. void markUnpinned(std::vector<Entry>::iterator i);
  99. int resizeGetHeight(int width) override;
  100. [[nodiscard]] auto pinnedSavedGifts()
  101. -> Fn<std::vector<Data::CreditsHistoryEntry>()>;
  102. const not_null<Window::SessionController*> _window;
  103. rpl::variable<Filter> _filter;
  104. Delegate _delegate;
  105. not_null<Controller*> _controller;
  106. std::unique_ptr<Ui::FlatLabel> _about;
  107. const not_null<PeerData*> _peer;
  108. std::vector<Entry> _entries;
  109. int _totalCount = 0;
  110. rpl::event_stream<> _scrollToTop;
  111. MTP::Sender _api;
  112. mtpRequestId _loadMoreRequestId = 0;
  113. QString _offset;
  114. bool _allLoaded = false;
  115. bool _reloading = false;
  116. rpl::event_stream<bool> _notifyEnabled;
  117. std::vector<View> _views;
  118. int _viewsForWidth = 0;
  119. int _viewsFromRow = 0;
  120. int _viewsTillRow = 0;
  121. QSize _singleMin;
  122. QSize _single;
  123. int _perRow = 0;
  124. int _visibleFrom = 0;
  125. int _visibleTill = 0;
  126. base::unique_qptr<Ui::PopupMenu> _menu;
  127. };
  128. InnerWidget::InnerWidget(
  129. QWidget *parent,
  130. not_null<Controller*> controller,
  131. not_null<PeerData*> peer,
  132. rpl::producer<Filter> filter)
  133. : BoxContentDivider(parent)
  134. , _window(controller->parentController())
  135. , _filter(std::move(filter))
  136. , _delegate(&_window->session(), GiftButtonMode::Minimal)
  137. , _controller(controller)
  138. , _peer(peer)
  139. , _totalCount(_peer->peerGiftsCount())
  140. , _api(&_peer->session().mtp()) {
  141. _singleMin = _delegate.buttonSize();
  142. if (peer->canManageGifts()) {
  143. subscribeToUpdates();
  144. }
  145. _filter.value() | rpl::start_with_next([=] {
  146. _reloading = true;
  147. _api.request(base::take(_loadMoreRequestId)).cancel();
  148. _allLoaded = false;
  149. refreshAbout();
  150. loadMore();
  151. }, lifetime());
  152. }
  153. void InnerWidget::subscribeToUpdates() {
  154. _peer->owner().giftUpdates(
  155. ) | rpl::start_with_next([=](const Data::GiftUpdate &update) {
  156. const auto savedId = [](const Entry &entry) {
  157. return entry.gift.manageId;
  158. };
  159. const auto i = ranges::find(_entries, update.id, savedId);
  160. if (i == end(_entries)) {
  161. return;
  162. }
  163. const auto index = int(i - begin(_entries));
  164. using Action = Data::GiftUpdate::Action;
  165. if (update.action == Action::Convert
  166. || update.action == Action::Transfer
  167. || update.action == Action::Delete) {
  168. _entries.erase(i);
  169. if (_totalCount > 0) {
  170. --_totalCount;
  171. }
  172. for (auto &view : _views) {
  173. if (view.index >= index) {
  174. --view.index;
  175. }
  176. }
  177. } else if (update.action == Action::Save
  178. || update.action == Action::Unsave) {
  179. i->gift.hidden = (update.action == Action::Unsave);
  180. const auto unpin = i->gift.hidden && i->gift.pinned;
  181. v::match(i->descriptor, [](GiftTypePremium &) {
  182. }, [&](GiftTypeStars &data) {
  183. data.hidden = i->gift.hidden;
  184. });
  185. for (auto &view : _views) {
  186. if (view.index == index) {
  187. view.index = -1;
  188. view.manageId = {};
  189. }
  190. }
  191. if (unpin) {
  192. markUnpinned(i);
  193. }
  194. } else if (update.action == Action::Pin
  195. || update.action == Action::Unpin) {
  196. if (update.action == Action::Pin) {
  197. markPinned(i);
  198. } else {
  199. markUnpinned(i);
  200. }
  201. } else {
  202. return;
  203. }
  204. refreshButtons();
  205. if (update.action == Action::Pin) {
  206. _scrollToTop.fire({});
  207. }
  208. }, lifetime());
  209. }
  210. void InnerWidget::markPinned(std::vector<Entry>::iterator i) {
  211. const auto index = int(i - begin(_entries));
  212. i->gift.pinned = true;
  213. v::match(i->descriptor, [](const GiftTypePremium &) {
  214. }, [&](GiftTypeStars &data) {
  215. data.pinned = true;
  216. });
  217. if (index) {
  218. std::rotate(begin(_entries), i, i + 1);
  219. }
  220. auto unpin = end(_entries);
  221. const auto session = &_window->session();
  222. const auto limit = session->appConfig().pinnedGiftsLimit();
  223. if (limit < _entries.size()) {
  224. const auto j = begin(_entries) + limit;
  225. if (j->gift.pinned) {
  226. unpin = j;
  227. }
  228. }
  229. for (auto &view : _views) {
  230. if (view.index <= index) {
  231. view.index = -1;
  232. view.manageId = {};
  233. }
  234. }
  235. if (unpin != end(_entries)) {
  236. markUnpinned(unpin);
  237. }
  238. }
  239. void InnerWidget::markUnpinned(std::vector<Entry>::iterator i) {
  240. const auto index = int(i - begin(_entries));
  241. i->gift.pinned = false;
  242. v::match(i->descriptor, [](const GiftTypePremium &) {
  243. }, [&](GiftTypeStars &data) {
  244. data.pinned = false;
  245. });
  246. auto after = index + 1;
  247. for (auto j = i + 1; j != end(_entries); ++j) {
  248. if (!j->gift.pinned && j->gift.date <= i->gift.date) {
  249. break;
  250. }
  251. ++after;
  252. }
  253. if (after == _entries.size() && !_allLoaded) {
  254. // We don't know if the correct position is exactly in the end
  255. // of the loaded part or later, so we hide it for now, let it
  256. // be loaded later while scrolling.
  257. _entries.erase(i);
  258. } else if (after > index + 1) {
  259. std::rotate(i, i + 1, begin(_entries) + after);
  260. }
  261. for (auto &view : _views) {
  262. if (view.index >= index) {
  263. view.index = -1;
  264. view.manageId = {};
  265. }
  266. }
  267. }
  268. void InnerWidget::visibleTopBottomUpdated(
  269. int visibleTop,
  270. int visibleBottom) {
  271. const auto page = (visibleBottom - visibleTop);
  272. if (visibleBottom + page * kPreloadPages >= height()) {
  273. loadMore();
  274. }
  275. _visibleFrom = visibleTop;
  276. _visibleTill = visibleBottom;
  277. validateButtons();
  278. }
  279. void InnerWidget::paintEvent(QPaintEvent *e) {
  280. auto p = QPainter(this);
  281. const auto aboutSize = _about
  282. ? _about->size().grownBy(st::giftListAboutMargin)
  283. : QSize();
  284. const auto skips = QMargins(0, 0, 0, aboutSize.height());
  285. p.fillRect(rect().marginsRemoved(skips), st::boxDividerBg->c);
  286. paintTop(p);
  287. if (const auto bottom = skips.bottom()) {
  288. paintBottom(p, bottom);
  289. }
  290. }
  291. void InnerWidget::loadMore() {
  292. if (_allLoaded || _loadMoreRequestId) {
  293. return;
  294. }
  295. using Flag = MTPpayments_GetSavedStarGifts::Flag;
  296. const auto filter = _filter.current();
  297. _loadMoreRequestId = _api.request(MTPpayments_GetSavedStarGifts(
  298. MTP_flags((filter.sortByValue ? Flag::f_sort_by_value : Flag())
  299. | (filter.skipLimited ? Flag::f_exclude_limited : Flag())
  300. | (filter.skipUnlimited ? Flag::f_exclude_unlimited : Flag())
  301. | (filter.skipUnique ? Flag::f_exclude_unique : Flag())
  302. | (filter.skipSaved ? Flag::f_exclude_saved : Flag())
  303. | (filter.skipUnsaved ? Flag::f_exclude_unsaved : Flag())),
  304. _peer->input,
  305. MTP_string(_reloading ? QString() : _offset),
  306. MTP_int(kPerPage)
  307. )).done([=](const MTPpayments_SavedStarGifts &result) {
  308. _loadMoreRequestId = 0;
  309. const auto &data = result.data();
  310. if (const auto enabled = data.vchat_notifications_enabled()) {
  311. _notifyEnabled.fire(mtpIsTrue(*enabled));
  312. }
  313. if (const auto next = data.vnext_offset()) {
  314. _offset = qs(*next);
  315. } else {
  316. _allLoaded = true;
  317. }
  318. _totalCount = data.vcount().v;
  319. const auto owner = &_peer->owner();
  320. owner->processUsers(data.vusers());
  321. owner->processChats(data.vchats());
  322. if (base::take(_reloading)) {
  323. _entries.clear();
  324. }
  325. _entries.reserve(_entries.size() + data.vgifts().v.size());
  326. for (const auto &gift : data.vgifts().v) {
  327. if (auto parsed = Api::FromTL(_peer, gift)) {
  328. auto descriptor = DescriptorForGift(_peer, *parsed);
  329. _entries.push_back({
  330. .gift = std::move(*parsed),
  331. .descriptor = std::move(descriptor),
  332. });
  333. }
  334. }
  335. refreshButtons();
  336. refreshAbout();
  337. }).fail([=] {
  338. _loadMoreRequestId = 0;
  339. _allLoaded = true;
  340. }).send();
  341. }
  342. void InnerWidget::refreshButtons() {
  343. _viewsForWidth = 0;
  344. _viewsFromRow = 0;
  345. _viewsTillRow = 0;
  346. resizeToWidth(width());
  347. validateButtons();
  348. }
  349. void InnerWidget::validateButtons() {
  350. if (!_perRow) {
  351. return;
  352. }
  353. const auto padding = st::giftBoxPadding;
  354. const auto vskip = padding.bottom();
  355. const auto row = _single.height() + st::giftBoxGiftSkip.y();
  356. const auto fromRow = std::max(_visibleFrom - vskip, 0) / row;
  357. const auto tillRow = (_visibleTill - vskip + row - 1) / row;
  358. Assert(tillRow >= fromRow);
  359. if (_viewsFromRow == fromRow
  360. && _viewsTillRow == tillRow
  361. && _viewsForWidth == width()) {
  362. return;
  363. }
  364. _viewsFromRow = fromRow;
  365. _viewsTillRow = tillRow;
  366. _viewsForWidth = width();
  367. const auto available = _viewsForWidth - padding.left() - padding.right();
  368. const auto skipw = st::giftBoxGiftSkip.x();
  369. const auto fullw = _perRow * (_single.width() + skipw) - skipw;
  370. const auto left = padding.left() + (available - fullw) / 2;
  371. const auto oneh = _single.height() + st::giftBoxGiftSkip.y();
  372. const auto mode = GiftButton::Mode::Minimal;
  373. auto x = left;
  374. auto y = vskip + fromRow * oneh;
  375. auto views = std::vector<View>();
  376. views.reserve((tillRow - fromRow) * _perRow);
  377. const auto idUsed = [&](uint64 giftId, int column, int row) {
  378. for (auto j = row; j != tillRow; ++j) {
  379. for (auto i = column; i != _perRow; ++i) {
  380. const auto index = j * _perRow + i;
  381. if (index >= _entries.size()) {
  382. return false;
  383. } else if (_entries[index].gift.info.id == giftId) {
  384. return true;
  385. }
  386. }
  387. column = 0;
  388. }
  389. return false;
  390. };
  391. const auto add = [&](int column, int row) {
  392. const auto index = row * _perRow + column;
  393. if (index >= _entries.size()) {
  394. return false;
  395. }
  396. const auto giftId = _entries[index].gift.info.id;
  397. const auto manageId = _entries[index].gift.manageId;
  398. const auto &descriptor = _entries[index].descriptor;
  399. const auto already = ranges::find(_views, giftId, &View::giftId);
  400. if (already != end(_views)) {
  401. views.push_back(base::take(*already));
  402. } else {
  403. const auto unused = ranges::find_if(_views, [&](const View &v) {
  404. return v.button && !idUsed(v.giftId, column, row);
  405. });
  406. if (unused != end(_views)) {
  407. views.push_back(base::take(*unused));
  408. } else {
  409. auto button = std::make_unique<GiftButton>(this, &_delegate);
  410. const auto raw = button.get();
  411. raw->contextMenuRequests(
  412. ) | rpl::start_with_next([=](QPoint point) {
  413. showMenuFor(raw, point);
  414. }, raw->lifetime());
  415. raw->show();
  416. views.push_back({ .button = std::move(button) });
  417. }
  418. }
  419. auto &view = views.back();
  420. const auto callback = [=] {
  421. showGift(index);
  422. };
  423. view.index = index;
  424. view.manageId = manageId;
  425. view.giftId = giftId;
  426. view.button->setDescriptor(descriptor, mode);
  427. view.button->setClickedCallback(callback);
  428. return true;
  429. };
  430. for (auto j = fromRow; j != tillRow; ++j) {
  431. for (auto i = 0; i != _perRow; ++i) {
  432. if (!add(i, j)) {
  433. break;
  434. }
  435. views.back().button->setGeometry(
  436. QRect(QPoint(x, y), _single),
  437. _delegate.buttonExtend());
  438. x += _single.width() + skipw;
  439. }
  440. x = left;
  441. y += oneh;
  442. }
  443. std::swap(_views, views);
  444. }
  445. auto InnerWidget::pinnedSavedGifts()
  446. -> Fn<std::vector<Data::CreditsHistoryEntry>()> {
  447. struct Entry {
  448. Data::SavedStarGiftId id;
  449. std::shared_ptr<Data::UniqueGift> unique;
  450. };
  451. auto entries = std::vector<Entry>();
  452. for (const auto &entry : _entries) {
  453. if (entry.gift.pinned) {
  454. Assert(entry.gift.info.unique != nullptr);
  455. entries.push_back({
  456. entry.gift.manageId,
  457. entry.gift.info.unique,
  458. });
  459. } else {
  460. break;
  461. }
  462. }
  463. return [entries] {
  464. auto result = std::vector<Data::CreditsHistoryEntry>();
  465. result.reserve(entries.size());
  466. for (const auto &entry : entries) {
  467. const auto &id = entry.id;
  468. result.push_back({
  469. .bareMsgId = uint64(id.userMessageId().bare),
  470. .bareEntryOwnerId = id.chat() ? id.chat()->id.value : 0,
  471. .giftChannelSavedId = id.chatSavedId(),
  472. .uniqueGift = entry.unique,
  473. .stargift = true,
  474. });
  475. }
  476. return result;
  477. };
  478. }
  479. void InnerWidget::showMenuFor(not_null<GiftButton*> button, QPoint point) {
  480. if (_menu) {
  481. return;
  482. }
  483. const auto index = [&] {
  484. for (const auto &view : _views) {
  485. if (view.button.get() == button) {
  486. return view.index;
  487. }
  488. }
  489. return -1;
  490. }();
  491. if (index < 0) {
  492. return;
  493. }
  494. auto entry = ::Settings::SavedStarGiftEntry(
  495. _peer,
  496. _entries[index].gift);
  497. entry.pinnedSavedGifts = pinnedSavedGifts();
  498. _menu = base::make_unique_q<Ui::PopupMenu>(this, st::popupMenuWithIcons);
  499. ::Settings::FillSavedStarGiftMenu(
  500. _controller->uiShow(),
  501. _menu.get(),
  502. entry,
  503. ::Settings::SavedStarGiftMenuType::List);
  504. if (_menu->empty()) {
  505. return;
  506. }
  507. _menu->popup(point);
  508. }
  509. void InnerWidget::showGift(int index) {
  510. Expects(index >= 0 && index < _entries.size());
  511. _window->show(Box(
  512. ::Settings::SavedStarGiftBox,
  513. _window,
  514. _peer,
  515. _entries[index].gift,
  516. pinnedSavedGifts()));
  517. }
  518. void InnerWidget::refreshAbout() {
  519. if (!_peer->isSelf() && _peer->canManageGifts() && !_entries.empty()) {
  520. if (_about) {
  521. _about = nullptr;
  522. resizeToWidth(width());
  523. }
  524. } else if (!_about) {
  525. _about = std::make_unique<Ui::FlatLabel>(
  526. this,
  527. (_peer->isSelf()
  528. ? tr::lng_peer_gifts_about_mine(Ui::Text::RichLangValue)
  529. : tr::lng_peer_gifts_about(
  530. lt_user,
  531. rpl::single(Ui::Text::Bold(_peer->shortName())),
  532. Ui::Text::RichLangValue)),
  533. st::giftListAbout);
  534. _about->show();
  535. resizeToWidth(width());
  536. }
  537. }
  538. int InnerWidget::resizeGetHeight(int width) {
  539. const auto count = int(_entries.size());
  540. const auto padding = st::giftBoxPadding;
  541. const auto available = width - padding.left() - padding.right();
  542. const auto skipw = st::giftBoxGiftSkip.x();
  543. _perRow = std::min(
  544. (available + skipw) / (_singleMin.width() + skipw),
  545. std::max(count, 1));
  546. if (!_perRow) {
  547. return 0;
  548. }
  549. const auto singlew = std::min(
  550. ((available + skipw) / _perRow) - skipw,
  551. 2 * _singleMin.width());
  552. Assert(singlew >= _singleMin.width());
  553. const auto singleh = _singleMin.height();
  554. _single = QSize(singlew, singleh);
  555. const auto rows = (count + _perRow - 1) / _perRow;
  556. const auto skiph = st::giftBoxGiftSkip.y();
  557. auto result = rows
  558. ? (padding.bottom() * 2 + rows * (singleh + skiph) - skiph)
  559. : 0;
  560. if (const auto about = _about.get()) {
  561. const auto margin = st::giftListAboutMargin;
  562. about->resizeToWidth(width - margin.left() - margin.right());
  563. about->moveToLeft(margin.left(), result + margin.top());
  564. result += margin.top() + about->height() + margin.bottom();
  565. }
  566. return result;
  567. }
  568. void InnerWidget::saveState(not_null<Memento*> memento) {
  569. auto state = std::make_unique<ListState>();
  570. memento->setListState(std::move(state));
  571. }
  572. void InnerWidget::restoreState(not_null<Memento*> memento) {
  573. if (const auto state = memento->listState()) {
  574. }
  575. }
  576. Memento::Memento(not_null<PeerData*> peer)
  577. : ContentMemento(peer, nullptr, PeerId()) {
  578. }
  579. Section Memento::section() const {
  580. return Section(Section::Type::PeerGifts);
  581. }
  582. object_ptr<ContentWidget> Memento::createWidget(
  583. QWidget *parent,
  584. not_null<Controller*> controller,
  585. const QRect &geometry) {
  586. auto result = object_ptr<Widget>(parent, controller, peer());
  587. result->setInternalState(geometry, this);
  588. return result;
  589. }
  590. void Memento::setListState(std::unique_ptr<ListState> state) {
  591. _listState = std::move(state);
  592. }
  593. std::unique_ptr<ListState> Memento::listState() {
  594. return std::move(_listState);
  595. }
  596. Memento::~Memento() = default;
  597. Widget::Widget(
  598. QWidget *parent,
  599. not_null<Controller*> controller,
  600. not_null<PeerData*> peer)
  601. : ContentWidget(parent, controller) {
  602. _inner = setInnerWidget(
  603. object_ptr<InnerWidget>(this, controller, peer, _filter.value()));
  604. _inner->notifyEnabled(
  605. ) | rpl::take(1) | rpl::start_with_next([=](bool enabled) {
  606. setupNotifyCheckbox(enabled);
  607. }, _inner->lifetime());
  608. _inner->scrollToTop() | rpl::start_with_next([=] {
  609. scrollTo({ 0, 0 });
  610. }, _inner->lifetime());
  611. }
  612. void Widget::showFinished() {
  613. _shown = true;
  614. if (const auto bottom = _pinnedToBottom.data()) {
  615. bottom->toggle(true, anim::type::normal);
  616. }
  617. }
  618. void Widget::setupNotifyCheckbox(bool enabled) {
  619. _pinnedToBottom = Ui::CreateChild<Ui::SlideWrap<Ui::RpWidget>>(
  620. this,
  621. object_ptr<Ui::RpWidget>(this));
  622. const auto wrap = _pinnedToBottom.data();
  623. wrap->toggle(false, anim::type::instant);
  624. const auto bottom = wrap->entity();
  625. bottom->show();
  626. const auto notify = Ui::CreateChild<Ui::Checkbox>(
  627. bottom,
  628. tr::lng_peer_gifts_notify(),
  629. enabled);
  630. notify->show();
  631. notify->checkedChanges() | rpl::start_with_next([=](bool checked) {
  632. const auto api = &controller()->session().api();
  633. const auto show = controller()->uiShow();
  634. using Flag = MTPpayments_ToggleChatStarGiftNotifications::Flag;
  635. api->request(MTPpayments_ToggleChatStarGiftNotifications(
  636. MTP_flags(checked ? Flag::f_enabled : Flag()),
  637. _inner->peer()->input
  638. )).send();
  639. if (checked) {
  640. show->showToast(tr::lng_peer_gifts_notify_enabled(tr::now));
  641. }
  642. }, notify->lifetime());
  643. const auto &checkSt = st::defaultCheckbox;
  644. const auto checkTop = st::boxRadius + checkSt.margin.top();
  645. bottom->widthValue() | rpl::start_with_next([=](int width) {
  646. const auto normal = notify->naturalWidth()
  647. - checkSt.margin.left()
  648. - checkSt.margin.right();
  649. notify->resizeToWidth(normal);
  650. const auto checkLeft = (width - normal) / 2;
  651. notify->moveToLeft(checkLeft, checkTop);
  652. }, notify->lifetime());
  653. notify->heightValue() | rpl::start_with_next([=](int height) {
  654. bottom->resize(bottom->width(), st::boxRadius + height);
  655. }, notify->lifetime());
  656. const auto processHeight = [=] {
  657. setScrollBottomSkip(wrap->height());
  658. wrap->moveToLeft(wrap->x(), height() - wrap->height());
  659. };
  660. _inner->sizeValue(
  661. ) | rpl::start_with_next([=](const QSize &s) {
  662. wrap->resizeToWidth(s.width());
  663. crl::on_main(wrap, processHeight);
  664. }, wrap->lifetime());
  665. rpl::combine(
  666. wrap->heightValue(),
  667. heightValue()
  668. ) | rpl::start_with_next(processHeight, wrap->lifetime());
  669. if (_shown) {
  670. wrap->toggle(true, anim::type::normal);
  671. }
  672. _hasPinnedToBottom = true;
  673. }
  674. void Widget::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
  675. const auto filter = _filter.current();
  676. const auto change = [=](Fn<void(Filter&)> update) {
  677. auto now = _filter.current();
  678. update(now);
  679. _filter = now;
  680. };
  681. if (filter.sortByValue) {
  682. addAction(tr::lng_peer_gifts_filter_by_date(tr::now), [=] {
  683. change([](Filter &filter) { filter.sortByValue = false; });
  684. }, &st::menuIconSchedule);
  685. } else {
  686. addAction(tr::lng_peer_gifts_filter_by_value(tr::now), [=] {
  687. change([](Filter &filter) { filter.sortByValue = true; });
  688. }, &st::menuIconEarn);
  689. }
  690. addAction({ .isSeparator = true });
  691. addAction(tr::lng_peer_gifts_filter_unlimited(tr::now), [=] {
  692. change([](Filter &filter) {
  693. filter.skipUnlimited = !filter.skipUnlimited;
  694. if (filter.skipUnlimited
  695. && filter.skipLimited
  696. && filter.skipUnique) {
  697. filter.skipLimited = false;
  698. }
  699. });
  700. }, filter.skipUnlimited ? nullptr : &st::mediaPlayerMenuCheck);
  701. addAction(tr::lng_peer_gifts_filter_limited(tr::now), [=] {
  702. change([](Filter &filter) {
  703. filter.skipLimited = !filter.skipLimited;
  704. if (filter.skipUnlimited
  705. && filter.skipLimited
  706. && filter.skipUnique) {
  707. filter.skipUnlimited = false;
  708. }
  709. });
  710. }, filter.skipLimited ? nullptr : &st::mediaPlayerMenuCheck);
  711. addAction(tr::lng_peer_gifts_filter_unique(tr::now), [=] {
  712. change([](Filter &filter) {
  713. filter.skipUnique = !filter.skipUnique;
  714. if (filter.skipUnlimited
  715. && filter.skipLimited
  716. && filter.skipUnique) {
  717. filter.skipUnlimited = false;
  718. }
  719. });
  720. }, filter.skipUnique ? nullptr : &st::mediaPlayerMenuCheck);
  721. if (_inner->peer()->canManageGifts()) {
  722. addAction({ .isSeparator = true });
  723. addAction(tr::lng_peer_gifts_filter_saved(tr::now), [=] {
  724. change([](Filter &filter) {
  725. filter.skipSaved = !filter.skipSaved;
  726. if (filter.skipSaved && filter.skipUnsaved) {
  727. filter.skipUnsaved = false;
  728. }
  729. });
  730. }, filter.skipSaved ? nullptr : &st::mediaPlayerMenuCheck);
  731. addAction(tr::lng_peer_gifts_filter_unsaved(tr::now), [=] {
  732. change([](Filter &filter) {
  733. filter.skipUnsaved = !filter.skipUnsaved;
  734. if (filter.skipSaved && filter.skipUnsaved) {
  735. filter.skipSaved = false;
  736. }
  737. });
  738. }, filter.skipUnsaved ? nullptr : &st::mediaPlayerMenuCheck);
  739. }
  740. }
  741. rpl::producer<QString> Widget::title() {
  742. return tr::lng_peer_gifts_title();
  743. }
  744. rpl::producer<bool> Widget::desiredBottomShadowVisibility() {
  745. return _hasPinnedToBottom.value();
  746. }
  747. not_null<PeerData*> Widget::peer() const {
  748. return _inner->peer();
  749. }
  750. bool Widget::showInternal(not_null<ContentMemento*> memento) {
  751. if (!controller()->validateMementoPeer(memento)) {
  752. return false;
  753. }
  754. if (auto similarMemento = dynamic_cast<Memento*>(memento.get())) {
  755. if (similarMemento->peer() == peer()) {
  756. restoreState(similarMemento);
  757. return true;
  758. }
  759. }
  760. return false;
  761. }
  762. void Widget::setInternalState(
  763. const QRect &geometry,
  764. not_null<Memento*> memento) {
  765. setGeometry(geometry);
  766. Ui::SendPendingMoveResizeEvents(this);
  767. restoreState(memento);
  768. }
  769. std::shared_ptr<ContentMemento> Widget::doCreateMemento() {
  770. auto result = std::make_shared<Memento>(peer());
  771. saveState(result.get());
  772. return result;
  773. }
  774. void Widget::saveState(not_null<Memento*> memento) {
  775. memento->setScrollTop(scrollTopSave());
  776. _inner->saveState(memento);
  777. }
  778. void Widget::restoreState(not_null<Memento*> memento) {
  779. _inner->restoreState(memento);
  780. scrollTopRestore(memento->scrollTop());
  781. }
  782. } // namespace Info::PeerGifts