settings_credits.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  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_credits.h"
  8. #include "api/api_credits.h"
  9. #include "base/call_delayed.h"
  10. #include "boxes/star_gift_box.h"
  11. #include "boxes/gift_credits_box.h"
  12. #include "boxes/gift_premium_box.h"
  13. #include "core/click_handler_types.h"
  14. #include "data/components/credits.h"
  15. #include "data/data_file_origin.h"
  16. #include "data/data_photo_media.h"
  17. #include "data/data_session.h"
  18. #include "data/data_user.h"
  19. #include "info/bot/starref/info_bot_starref_common.h"
  20. #include "info/bot/starref/info_bot_starref_join_widget.h"
  21. #include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
  22. #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
  23. #include "info/statistics/info_statistics_list_controllers.h"
  24. #include "info/info_memento.h"
  25. #include "lang/lang_keys.h"
  26. #include "main/main_session.h"
  27. #include "settings/settings_common_session.h"
  28. #include "settings/settings_credits_graphics.h"
  29. #include "statistics/widgets/chart_header_widget.h"
  30. #include "ui/boxes/boost_box.h" // Ui::StartFireworks.
  31. #include "ui/effects/credits_graphics.h"
  32. #include "ui/effects/premium_graphics.h"
  33. #include "ui/effects/premium_top_bar.h"
  34. #include "ui/layers/generic_box.h"
  35. #include "ui/painter.h"
  36. #include "ui/rect.h"
  37. #include "ui/text/text_utilities.h"
  38. #include "ui/vertical_list.h"
  39. #include "ui/widgets/buttons.h"
  40. #include "ui/widgets/slider_natural_width.h"
  41. #include "ui/wrap/fade_wrap.h"
  42. #include "ui/wrap/slide_wrap.h"
  43. #include "ui/wrap/vertical_layout.h"
  44. #include "window/window_session_controller.h"
  45. #include "styles/style_chat_helpers.h"
  46. #include "styles/style_credits.h"
  47. #include "styles/style_giveaway.h"
  48. #include "styles/style_info.h"
  49. #include "styles/style_layers.h"
  50. #include "styles/style_premium.h"
  51. #include "styles/style_settings.h"
  52. #include "styles/style_statistics.h"
  53. namespace Settings {
  54. namespace {
  55. class Credits : public Section<Credits> {
  56. public:
  57. Credits(
  58. QWidget *parent,
  59. not_null<Window::SessionController*> controller);
  60. [[nodiscard]] rpl::producer<QString> title() override;
  61. [[nodiscard]] QPointer<Ui::RpWidget> createPinnedToTop(
  62. not_null<QWidget*> parent) override;
  63. void showFinished() override;
  64. [[nodiscard]] bool hasFlexibleTopBar() const override;
  65. void setStepDataReference(std::any &data) override;
  66. [[nodiscard]] rpl::producer<> sectionShowBack() override final;
  67. private:
  68. void setupContent();
  69. void setupHistory(not_null<Ui::VerticalLayout*> container);
  70. void setupSubscriptions(not_null<Ui::VerticalLayout*> container);
  71. void setupStarRefPromo(not_null<Ui::VerticalLayout*> container);
  72. const not_null<Window::SessionController*> _controller;
  73. QWidget *_parent = nullptr;
  74. QImage _star;
  75. QImage _balanceStar;
  76. base::unique_qptr<Ui::FadeWrap<Ui::IconButton>> _back;
  77. base::unique_qptr<Ui::IconButton> _close;
  78. rpl::variable<bool> _backToggles;
  79. rpl::variable<Info::Wrap> _wrap;
  80. Fn<void(bool)> _setPaused;
  81. rpl::event_stream<> _showBack;
  82. rpl::event_stream<> _showFinished;
  83. rpl::variable<QString> _buttonText;
  84. };
  85. Credits::Credits(
  86. QWidget *parent,
  87. not_null<Window::SessionController*> controller)
  88. : Section(parent)
  89. , _controller(controller)
  90. , _star(Ui::GenerateStars(st::creditsTopupButton.height, 1))
  91. , _balanceStar(Ui::GenerateStars(st::creditsBalanceStarHeight, 1)) {
  92. setupContent();
  93. _controller->session().premiumPossibleValue(
  94. ) | rpl::start_with_next([=](bool premiumPossible) {
  95. if (!premiumPossible) {
  96. _showBack.fire({});
  97. }
  98. }, lifetime());
  99. }
  100. rpl::producer<QString> Credits::title() {
  101. return tr::lng_premium_summary_title();
  102. }
  103. bool Credits::hasFlexibleTopBar() const {
  104. return true;
  105. }
  106. rpl::producer<> Credits::sectionShowBack() {
  107. return _showBack.events();
  108. }
  109. void Credits::setStepDataReference(std::any &data) {
  110. using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData;
  111. const auto my = std::any_cast<SectionCustomTopBarData>(&data);
  112. if (my) {
  113. _backToggles = std::move(
  114. my->backButtonEnables
  115. ) | rpl::map_to(true);
  116. _wrap = std::move(my->wrapValue);
  117. }
  118. }
  119. void Credits::setupSubscriptions(not_null<Ui::VerticalLayout*> container) {
  120. const auto history = container->add(
  121. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  122. container,
  123. object_ptr<Ui::VerticalLayout>(container)));
  124. const auto content = history->entity();
  125. const auto self = _controller->session().user();
  126. const auto fill = [=](const Data::CreditsStatusSlice &fullSlice) {
  127. const auto inner = content;
  128. if (fullSlice.subscriptions.empty()) {
  129. return;
  130. }
  131. Ui::AddSkip(inner);
  132. Ui::AddSubsectionTitle(
  133. inner,
  134. tr::lng_credits_subscription_section(),
  135. { 0, 0, 0, -st::settingsPremiumOptionsPadding.bottom() });
  136. const auto fullWrap = inner->add(
  137. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  138. inner,
  139. object_ptr<Ui::VerticalLayout>(inner)));
  140. const auto controller = _controller->parentController();
  141. const auto entryClicked = [=](
  142. const Data::CreditsHistoryEntry &e,
  143. const Data::SubscriptionEntry &s) {
  144. controller->uiShow()->show(
  145. Box(ReceiptCreditsBox, controller, e, s));
  146. };
  147. Info::Statistics::AddCreditsHistoryList(
  148. controller->uiShow(),
  149. fullSlice,
  150. fullWrap->entity(),
  151. entryClicked,
  152. self,
  153. true,
  154. true,
  155. true);
  156. Ui::AddSkip(inner);
  157. Ui::AddSkip(inner);
  158. Ui::AddDivider(inner);
  159. inner->resizeToWidth(container->width());
  160. };
  161. const auto apiLifetime = content->lifetime().make_state<rpl::lifetime>();
  162. {
  163. using Api = Api::CreditsHistory;
  164. const auto apiFull = apiLifetime->make_state<Api>(self, true, true);
  165. apiFull->requestSubscriptions({}, [=](Data::CreditsStatusSlice d) {
  166. fill(std::move(d));
  167. });
  168. }
  169. {
  170. using Rebuilder = Data::Session::CreditsSubsRebuilder;
  171. using RebuilderPtr = std::shared_ptr<Rebuilder>;
  172. const auto rebuilder = content->lifetime().make_state<RebuilderPtr>(
  173. self->owner().createCreditsSubsRebuilder());
  174. rebuilder->get()->events(
  175. ) | rpl::start_with_next([=](Data::CreditsStatusSlice slice) {
  176. while (content->count()) {
  177. delete content->widgetAt(0);
  178. }
  179. fill(std::move(slice));
  180. }, content->lifetime());
  181. }
  182. }
  183. void Credits::setupStarRefPromo(not_null<Ui::VerticalLayout*> container) {
  184. const auto self = _controller->session().user();
  185. if (!Info::BotStarRef::Join::Allowed(self)) {
  186. return;
  187. }
  188. Ui::AddSkip(container);
  189. const auto button = Info::BotStarRef::AddViewListButton(
  190. container,
  191. tr::lng_credits_summary_earn_title(),
  192. tr::lng_credits_summary_earn_about(),
  193. true);
  194. button->setClickedCallback([=] {
  195. _controller->showSection(Info::BotStarRef::Join::Make(self));
  196. });
  197. Ui::AddSkip(container);
  198. Ui::AddDivider(container);
  199. Ui::AddSkip(container);
  200. }
  201. void Credits::setupHistory(not_null<Ui::VerticalLayout*> container) {
  202. const auto history = container->add(
  203. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  204. container,
  205. object_ptr<Ui::VerticalLayout>(container)));
  206. const auto content = history->entity();
  207. const auto self = _controller->session().user();
  208. Ui::AddSkip(content);
  209. const auto fill = [=](
  210. not_null<PeerData*> premiumBot,
  211. const Data::CreditsStatusSlice &fullSlice,
  212. const Data::CreditsStatusSlice &inSlice,
  213. const Data::CreditsStatusSlice &outSlice) {
  214. const auto inner = content;
  215. if (fullSlice.list.empty()) {
  216. return;
  217. }
  218. const auto hasOneTab = inSlice.list.empty() && outSlice.list.empty();
  219. const auto hasIn = !inSlice.list.empty();
  220. const auto hasOut = !outSlice.list.empty();
  221. const auto fullTabText = tr::lng_credits_summary_history_tab_full(
  222. tr::now);
  223. const auto inTabText = tr::lng_credits_summary_history_tab_in(
  224. tr::now);
  225. const auto outTabText = tr::lng_credits_summary_history_tab_out(
  226. tr::now);
  227. if (hasOneTab) {
  228. Ui::AddSubsectionTitle(
  229. inner,
  230. tr::lng_credits_summary_history_tab_full(),
  231. { 0, 0, 0, -st::defaultSubsectionTitlePadding.bottom() });
  232. }
  233. const auto slider = inner->add(
  234. object_ptr<Ui::SlideWrap<Ui::CustomWidthSlider>>(
  235. inner,
  236. object_ptr<Ui::CustomWidthSlider>(
  237. inner,
  238. st::defaultTabsSlider)),
  239. st::boxRowPadding);
  240. slider->toggle(!hasOneTab, anim::type::instant);
  241. slider->entity()->addSection(fullTabText);
  242. if (hasIn) {
  243. slider->entity()->addSection(inTabText);
  244. }
  245. if (hasOut) {
  246. slider->entity()->addSection(outTabText);
  247. }
  248. {
  249. const auto &st = st::defaultTabsSlider;
  250. slider->entity()->setNaturalWidth(0
  251. + st.labelStyle.font->width(fullTabText)
  252. + (hasIn ? st.labelStyle.font->width(inTabText) : 0)
  253. + (hasOut ? st.labelStyle.font->width(outTabText) : 0)
  254. + rect::m::sum::h(st::boxRowPadding));
  255. }
  256. const auto fullWrap = inner->add(
  257. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  258. inner,
  259. object_ptr<Ui::VerticalLayout>(inner)));
  260. const auto inWrap = inner->add(
  261. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  262. inner,
  263. object_ptr<Ui::VerticalLayout>(inner)));
  264. const auto outWrap = inner->add(
  265. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  266. inner,
  267. object_ptr<Ui::VerticalLayout>(inner)));
  268. rpl::single(0) | rpl::then(
  269. slider->entity()->sectionActivated()
  270. ) | rpl::start_with_next([=](int index) {
  271. if (index == 0) {
  272. fullWrap->toggle(true, anim::type::instant);
  273. inWrap->toggle(false, anim::type::instant);
  274. outWrap->toggle(false, anim::type::instant);
  275. } else if (index == 1) {
  276. inWrap->toggle(true, anim::type::instant);
  277. fullWrap->toggle(false, anim::type::instant);
  278. outWrap->toggle(false, anim::type::instant);
  279. } else {
  280. outWrap->toggle(true, anim::type::instant);
  281. fullWrap->toggle(false, anim::type::instant);
  282. inWrap->toggle(false, anim::type::instant);
  283. }
  284. }, inner->lifetime());
  285. const auto controller = _controller->parentController();
  286. const auto entryClicked = [=](
  287. const Data::CreditsHistoryEntry &e,
  288. const Data::SubscriptionEntry &s) {
  289. controller->uiShow()->show(Box(
  290. ReceiptCreditsBox,
  291. controller,
  292. e,
  293. s));
  294. };
  295. Info::Statistics::AddCreditsHistoryList(
  296. controller->uiShow(),
  297. fullSlice,
  298. fullWrap->entity(),
  299. entryClicked,
  300. self,
  301. true,
  302. true);
  303. Info::Statistics::AddCreditsHistoryList(
  304. controller->uiShow(),
  305. inSlice,
  306. inWrap->entity(),
  307. entryClicked,
  308. self,
  309. true,
  310. false);
  311. Info::Statistics::AddCreditsHistoryList(
  312. controller->uiShow(),
  313. outSlice,
  314. outWrap->entity(),
  315. std::move(entryClicked),
  316. self,
  317. false,
  318. true);
  319. Ui::AddSkip(inner);
  320. Ui::AddSkip(inner);
  321. inner->resizeToWidth(container->width());
  322. };
  323. const auto apiLifetime = content->lifetime().make_state<rpl::lifetime>();
  324. {
  325. using Api = Api::CreditsHistory;
  326. const auto apiFull = apiLifetime->make_state<Api>(self, true, true);
  327. const auto apiIn = apiLifetime->make_state<Api>(self, true, false);
  328. const auto apiOut = apiLifetime->make_state<Api>(self, false, true);
  329. apiFull->request({}, [=](Data::CreditsStatusSlice fullSlice) {
  330. apiIn->request({}, [=](Data::CreditsStatusSlice inSlice) {
  331. apiOut->request({}, [=](Data::CreditsStatusSlice outSlice) {
  332. ::Api::PremiumPeerBot(
  333. &_controller->session()
  334. ) | rpl::start_with_next([=](not_null<PeerData*> bot) {
  335. fill(bot, fullSlice, inSlice, outSlice);
  336. apiLifetime->destroy();
  337. }, *apiLifetime);
  338. });
  339. });
  340. });
  341. }
  342. }
  343. void Credits::setupContent() {
  344. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  345. const auto paid = [=] {
  346. if (_parent) {
  347. Ui::StartFireworks(_parent);
  348. }
  349. };
  350. Ui::AddSkip(content);
  351. Ui::AddSkip(content);
  352. const auto balanceLine = content->add(
  353. object_ptr<Ui::CenterWrap<>>(
  354. content,
  355. object_ptr<Ui::RpWidget>(content)))->entity();
  356. const auto balanceIcon = CreateSingleStarWidget(
  357. balanceLine,
  358. st::creditsSettingsBigBalance.style.font->height);
  359. const auto balanceAmount = Ui::CreateChild<Ui::FlatLabel>(
  360. balanceLine,
  361. _controller->session().credits().balanceValue(
  362. ) | rpl::map(Lang::FormatStarsAmountDecimal),
  363. st::creditsSettingsBigBalance);
  364. balanceAmount->sizeValue() | rpl::start_with_next([=] {
  365. balanceLine->resize(
  366. balanceIcon->width()
  367. + st::creditsSettingsBigBalanceSkip
  368. + balanceAmount->textMaxWidth(),
  369. balanceIcon->height());
  370. }, balanceLine->lifetime());
  371. balanceLine->widthValue() | rpl::start_with_next([=] {
  372. balanceAmount->moveToRight(0, 0);
  373. }, balanceLine->lifetime());
  374. Ui::AddSkip(content);
  375. content->add(
  376. object_ptr<Ui::CenterWrap<>>(
  377. content,
  378. object_ptr<Ui::FlatLabel>(
  379. content,
  380. tr::lng_credits_balance_me(),
  381. st::infoTopBar.subtitle)));
  382. Ui::AddSkip(content);
  383. Ui::AddSkip(content);
  384. Ui::AddSkip(content);
  385. struct State final {
  386. BuyStarsHandler buyStars;
  387. };
  388. const auto state = content->lifetime().make_state<State>();
  389. const auto button = content->add(
  390. object_ptr<Ui::RoundButton>(
  391. content,
  392. rpl::conditional(
  393. state->buyStars.loadingValue(),
  394. rpl::single(QString()),
  395. tr::lng_credits_buy_button()),
  396. st::creditsSettingsBigBalanceButton),
  397. st::boxRowPadding);
  398. button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  399. const auto show = _controller->uiShow();
  400. button->setClickedCallback(state->buyStars.handler(show, paid));
  401. {
  402. using namespace Info::Statistics;
  403. const auto loadingAnimation = InfiniteRadialAnimationWidget(
  404. button,
  405. button->height() / 2);
  406. AddChildToWidgetCenter(button, loadingAnimation);
  407. loadingAnimation->showOn(state->buyStars.loadingValue());
  408. }
  409. const auto paddings = rect::m::sum::h(st::boxRowPadding);
  410. button->widthValue() | rpl::filter([=] {
  411. return (button->widthNoMargins() != (content->width() - paddings));
  412. }) | rpl::start_with_next([=] {
  413. button->resizeToWidth(content->width() - paddings);
  414. }, button->lifetime());
  415. Ui::AddSkip(content);
  416. {
  417. const auto &giftSt = st::creditsSettingsBigBalanceButtonGift;
  418. const auto giftDelay = giftSt.ripple.hideDuration * 2;
  419. const auto fakeLoading
  420. = content->lifetime().make_state<rpl::variable<bool>>(false);
  421. const auto gift = content->add(
  422. object_ptr<Ui::RoundButton>(
  423. content,
  424. rpl::conditional(
  425. fakeLoading->value(),
  426. rpl::single(QString()),
  427. tr::lng_credits_gift_button()),
  428. giftSt),
  429. st::boxRowPadding);
  430. gift->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  431. gift->setClickedCallback([=, controller = _controller] {
  432. if (fakeLoading->current()) {
  433. return;
  434. }
  435. *fakeLoading = true;
  436. base::call_delayed(giftDelay, crl::guard(gift, [=] {
  437. *fakeLoading = false;
  438. Ui::ShowGiftCreditsBox(controller, paid);
  439. }));
  440. });
  441. {
  442. using namespace Info::Statistics;
  443. const auto loadingAnimation = InfiniteRadialAnimationWidget(
  444. gift,
  445. gift->height() / 2,
  446. &st::editStickerSetNameLoading);
  447. AddChildToWidgetCenter(gift, loadingAnimation);
  448. loadingAnimation->showOn(fakeLoading->value());
  449. }
  450. gift->widthValue() | rpl::filter([=] {
  451. return (gift->widthNoMargins() != (content->width() - paddings));
  452. }) | rpl::start_with_next([=] {
  453. gift->resizeToWidth(content->width() - paddings);
  454. }, gift->lifetime());
  455. }
  456. Ui::AddSkip(content);
  457. Ui::AddSkip(content);
  458. Ui::AddDivider(content);
  459. setupStarRefPromo(content);
  460. setupSubscriptions(content);
  461. setupHistory(content);
  462. Ui::ResizeFitChild(this, content);
  463. }
  464. QPointer<Ui::RpWidget> Credits::createPinnedToTop(
  465. not_null<QWidget*> parent) {
  466. _parent = parent;
  467. const auto content = [&]() -> Ui::Premium::TopBarAbstract* {
  468. const auto weak = base::make_weak(_controller);
  469. const auto clickContextOther = [=] {
  470. return QVariant::fromValue(ClickHandlerContext{
  471. .sessionWindow = weak,
  472. .botStartAutoSubmit = true,
  473. });
  474. };
  475. return Ui::CreateChild<Ui::Premium::TopBar>(
  476. parent.get(),
  477. st::creditsPremiumCover,
  478. Ui::Premium::TopBarDescriptor{
  479. .clickContextOther = clickContextOther,
  480. .title = tr::lng_credits_summary_title(),
  481. .about = tr::lng_credits_summary_about(
  482. TextWithEntities::Simple),
  483. .light = true,
  484. .gradientStops = Ui::Premium::CreditsIconGradientStops(),
  485. });
  486. }();
  487. _setPaused = [=](bool paused) {
  488. content->setPaused(paused);
  489. };
  490. _wrap.value(
  491. ) | rpl::start_with_next([=](Info::Wrap wrap) {
  492. content->setRoundEdges(wrap == Info::Wrap::Layer);
  493. }, content->lifetime());
  494. content->setMaximumHeight(st::settingsPremiumTopHeight);
  495. content->setMinimumHeight(st::infoLayerTopBarHeight);
  496. content->resize(content->width(), content->maximumHeight());
  497. content->additionalHeight(
  498. ) | rpl::start_with_next([=](int additionalHeight) {
  499. const auto wasMax = (content->height() == content->maximumHeight());
  500. content->setMaximumHeight(st::settingsPremiumTopHeight
  501. + additionalHeight);
  502. if (wasMax) {
  503. content->resize(content->width(), content->maximumHeight());
  504. }
  505. }, content->lifetime());
  506. {
  507. const auto balance = AddBalanceWidget(
  508. content,
  509. _controller->session().credits().balanceValue(),
  510. true,
  511. content->heightValue() | rpl::map([=](int height) {
  512. const auto ratio = float64(height - content->minimumHeight())
  513. / (content->maximumHeight() - content->minimumHeight());
  514. return (1. - ratio / 0.35);
  515. }));
  516. _controller->session().credits().load(true);
  517. rpl::combine(
  518. balance->sizeValue(),
  519. content->sizeValue()
  520. ) | rpl::start_with_next([=](const QSize &, const QSize &) {
  521. balance->moveToRight(
  522. (_close
  523. ? _close->width() + st::creditsHistoryRightSkip
  524. : st::creditsHistoryRightSkip * 2),
  525. st::creditsHistoryRightSkip);
  526. balance->update();
  527. }, balance->lifetime());
  528. }
  529. _wrap.value(
  530. ) | rpl::start_with_next([=](Info::Wrap wrap) {
  531. const auto isLayer = (wrap == Info::Wrap::Layer);
  532. _back = base::make_unique_q<Ui::FadeWrap<Ui::IconButton>>(
  533. content,
  534. object_ptr<Ui::IconButton>(
  535. content,
  536. (isLayer ? st::infoTopBarBack : st::infoLayerTopBarBack)),
  537. st::infoTopBarScale);
  538. _back->setDuration(0);
  539. _back->toggleOn(isLayer
  540. ? _backToggles.value() | rpl::type_erased()
  541. : rpl::single(true));
  542. _back->entity()->addClickHandler([=] {
  543. _showBack.fire({});
  544. });
  545. _back->toggledValue(
  546. ) | rpl::start_with_next([=](bool toggled) {
  547. const auto &st = isLayer ? st::infoLayerTopBar : st::infoTopBar;
  548. content->setTextPosition(
  549. toggled ? st.back.width : st.titlePosition.x(),
  550. st.titlePosition.y());
  551. }, _back->lifetime());
  552. if (!isLayer) {
  553. _close = nullptr;
  554. } else {
  555. _close = base::make_unique_q<Ui::IconButton>(
  556. content,
  557. st::infoTopBarClose);
  558. _close->addClickHandler([=] {
  559. _controller->parentController()->hideLayer();
  560. _controller->parentController()->hideSpecialLayer();
  561. });
  562. content->widthValue(
  563. ) | rpl::start_with_next([=] {
  564. _close->moveToRight(0, 0);
  565. }, _close->lifetime());
  566. }
  567. }, content->lifetime());
  568. return Ui::MakeWeak(not_null<Ui::RpWidget*>{ content });
  569. }
  570. void Credits::showFinished() {
  571. _showFinished.fire({});
  572. }
  573. } // namespace
  574. template <>
  575. struct SectionFactory<Credits> : AbstractSectionFactory {
  576. object_ptr<AbstractSection> create(
  577. not_null<QWidget*> parent,
  578. not_null<Window::SessionController*> controller,
  579. not_null<Ui::ScrollArea*> scroll,
  580. rpl::producer<Container> containerValue
  581. ) const final override {
  582. return object_ptr<Credits>(parent, controller);
  583. }
  584. bool hasCustomTopBar() const final override {
  585. return true;
  586. }
  587. [[nodiscard]] static const std::shared_ptr<SectionFactory> &Instance() {
  588. static const auto result = std::make_shared<SectionFactory>();
  589. return result;
  590. }
  591. };
  592. Type CreditsId() {
  593. return Credits::Id();
  594. }
  595. BuyStarsHandler::BuyStarsHandler() = default;
  596. BuyStarsHandler::~BuyStarsHandler() = default;
  597. Fn<void()> BuyStarsHandler::handler(
  598. std::shared_ptr<::Main::SessionShow> show,
  599. Fn<void()> paid) {
  600. const auto optionsBox = [=](not_null<Ui::GenericBox*> box) {
  601. box->setStyle(st::giveawayGiftCodeBox);
  602. box->setWidth(st::boxWideWidth);
  603. box->setTitle(tr::lng_credits_summary_options_subtitle());
  604. const auto inner = box->verticalLayout();
  605. const auto self = show->session().user();
  606. const auto options = _api
  607. ? _api->options()
  608. : Data::CreditTopupOptions();
  609. const auto amount = StarsAmount();
  610. const auto weak = Ui::MakeWeak(box);
  611. FillCreditOptions(show, inner, self, amount, [=] {
  612. if (const auto strong = weak.data()) {
  613. strong->closeBox();
  614. }
  615. if (const auto onstack = paid) {
  616. onstack();
  617. }
  618. }, nullptr, options);
  619. const auto button = box->addButton(tr::lng_close(), [=] {
  620. box->closeBox();
  621. });
  622. const auto buttonWidth = st::boxWideWidth
  623. - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
  624. button->widthValue() | rpl::filter([=] {
  625. return (button->widthNoMargins() != buttonWidth);
  626. }) | rpl::start_with_next([=] {
  627. button->resizeToWidth(buttonWidth);
  628. }, button->lifetime());
  629. };
  630. return crl::guard(this, [=] {
  631. if (_api && !_api->options().empty()) {
  632. _loading = false;
  633. show->show(Box(crl::guard(this, optionsBox)));
  634. } else {
  635. _loading = true;
  636. const auto user = show->session().user();
  637. _api = std::make_unique<Api::CreditsTopupOptions>(user);
  638. _api->request(
  639. ) | rpl::start_with_error_done([=](const QString &error) {
  640. _loading = false;
  641. show->showToast(error);
  642. }, [=] {
  643. _loading = false;
  644. show->show(Box(crl::guard(this, optionsBox)));
  645. }, _lifetime);
  646. }
  647. });
  648. }
  649. rpl::producer<bool> BuyStarsHandler::loadingValue() const {
  650. return _loading.value();
  651. }
  652. } // namespace Settings