boost_box.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  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 "ui/boxes/boost_box.h"
  8. #include "info/profile/info_profile_icon.h"
  9. #include "lang/lang_keys.h"
  10. #include "ui/boxes/confirm_box.h"
  11. #include "ui/effects/fireworks_animation.h"
  12. #include "ui/effects/premium_bubble.h"
  13. #include "ui/effects/premium_graphics.h"
  14. #include "ui/layers/generic_box.h"
  15. #include "ui/text/text_utilities.h"
  16. #include "ui/widgets/buttons.h"
  17. #include "ui/wrap/fade_wrap.h"
  18. #include "ui/painter.h"
  19. #include "styles/style_giveaway.h"
  20. #include "styles/style_layers.h"
  21. #include "styles/style_premium.h"
  22. #include <QtGui/QGuiApplication>
  23. namespace Ui {
  24. namespace {
  25. [[nodiscard]] BoostCounters AdjustByReached(BoostCounters data) {
  26. const auto exact = (data.boosts == data.thisLevelBoosts);
  27. const auto reached = !data.nextLevelBoosts || (exact && data.mine > 0);
  28. if (reached) {
  29. --data.level;
  30. data.boosts = data.nextLevelBoosts = std::max({
  31. data.boosts,
  32. data.thisLevelBoosts,
  33. 1
  34. });
  35. data.thisLevelBoosts = 0;
  36. } else {
  37. data.boosts = std::max(data.thisLevelBoosts, data.boosts);
  38. data.nextLevelBoosts = std::max(
  39. data.nextLevelBoosts,
  40. data.boosts + 1);
  41. }
  42. return data;
  43. }
  44. [[nodiscard]] object_ptr<Ui::RpWidget> MakeTitle(
  45. not_null<Ui::RpWidget*> parent,
  46. rpl::producer<QString> title,
  47. rpl::producer<QString> repeated,
  48. bool centered = true) {
  49. auto result = object_ptr<Ui::RpWidget>(parent);
  50. struct State {
  51. not_null<Ui::FlatLabel*> title;
  52. not_null<Ui::FlatLabel*> repeated;
  53. };
  54. const auto notEmpty = [](const QString &text) {
  55. return !text.isEmpty();
  56. };
  57. const auto state = parent->lifetime().make_state<State>(State{
  58. .title = Ui::CreateChild<Ui::FlatLabel>(
  59. result.data(),
  60. rpl::duplicate(title),
  61. st::boostTitle),
  62. .repeated = Ui::CreateChild<Ui::FlatLabel>(
  63. result.data(),
  64. rpl::duplicate(repeated) | rpl::filter(notEmpty),
  65. st::boostTitleBadge),
  66. });
  67. state->title->show();
  68. state->repeated->showOn(std::move(repeated) | rpl::map(notEmpty));
  69. result->resize(result->width(), st::boostTitle.style.font->height);
  70. rpl::combine(
  71. result->widthValue(),
  72. rpl::duplicate(title),
  73. state->repeated->shownValue(),
  74. state->repeated->widthValue()
  75. ) | rpl::start_with_next([=](int outer, auto&&, bool shown, int badge) {
  76. const auto repeated = shown ? badge : 0;
  77. const auto skip = st::boostTitleBadgeSkip;
  78. const auto available = outer - repeated - skip;
  79. const auto use = std::min(state->title->textMaxWidth(), available);
  80. state->title->resizeToWidth(use);
  81. const auto left = centered
  82. ? (outer - use - skip - repeated) / 2
  83. : 0;
  84. state->title->moveToLeft(left, 0);
  85. const auto mleft = st::boostTitleBadge.margin.left();
  86. const auto mtop = st::boostTitleBadge.margin.top();
  87. state->repeated->moveToLeft(left + use + skip + mleft, mtop);
  88. }, result->lifetime());
  89. const auto badge = state->repeated;
  90. badge->paintRequest() | rpl::start_with_next([=] {
  91. auto p = QPainter(badge);
  92. auto hq = PainterHighQualityEnabler(p);
  93. const auto radius = std::min(badge->width(), badge->height()) / 2;
  94. p.setPen(Qt::NoPen);
  95. p.setBrush(st::premiumButtonBg2);
  96. p.drawRoundedRect(badge->rect(), radius, radius);
  97. }, badge->lifetime());
  98. return result;
  99. }
  100. [[nodiscard]] object_ptr<Ui::FlatLabel> MakeFeaturesBadge(
  101. not_null<QWidget*> parent,
  102. rpl::producer<QString> text) {
  103. return MakeBoostFeaturesBadge(parent, std::move(text), [](QRect rect) {
  104. auto gradient = QLinearGradient(
  105. rect.topLeft(),
  106. rect.topRight());
  107. gradient.setStops(Ui::Premium::GiftGradientStops());
  108. return QBrush(gradient);
  109. });
  110. }
  111. void AddFeaturesList(
  112. not_null<Ui::VerticalLayout*> container,
  113. const Ui::BoostFeatures &features,
  114. int startFromLevel,
  115. bool group) {
  116. const auto add = [&](
  117. rpl::producer<TextWithEntities> text,
  118. const style::icon &st) {
  119. const auto label = container->add(
  120. object_ptr<Ui::FlatLabel>(
  121. container,
  122. std::move(text),
  123. st::boostFeatureLabel),
  124. st::boostFeaturePadding);
  125. object_ptr<Info::Profile::FloatingIcon>(
  126. label,
  127. st,
  128. st::boostFeatureIconPosition);
  129. };
  130. const auto proj = &Ui::Text::RichLangValue;
  131. const auto lowMax = std::max({
  132. features.linkLogoLevel,
  133. features.transcribeLevel,
  134. features.emojiPackLevel,
  135. features.emojiStatusLevel,
  136. features.wallpaperLevel,
  137. features.customWallpaperLevel,
  138. (features.nameColorsByLevel.empty()
  139. ? 0
  140. : features.nameColorsByLevel.back().first),
  141. (features.linkStylesByLevel.empty()
  142. ? 0
  143. : features.linkStylesByLevel.back().first),
  144. });
  145. const auto highMax = std::max(lowMax, features.sponsoredLevel);
  146. auto nameColors = 0;
  147. auto linkStyles = 0;
  148. for (auto i = std::max(startFromLevel, 1); i <= highMax; ++i) {
  149. if ((i > lowMax) && (i < highMax)) {
  150. continue;
  151. }
  152. const auto unlocks = (i == startFromLevel);
  153. container->add(
  154. MakeFeaturesBadge(
  155. container,
  156. (unlocks
  157. ? tr::lng_boost_level_unlocks
  158. : tr::lng_boost_level)(
  159. lt_count,
  160. rpl::single(float64(i)))),
  161. st::boostLevelBadgePadding);
  162. if (i >= features.sponsoredLevel) {
  163. add(tr::lng_channel_earn_off(proj), st::boostFeatureOffSponsored);
  164. }
  165. if (i >= features.customWallpaperLevel) {
  166. add(
  167. (group
  168. ? tr::lng_feature_custom_background_group
  169. : tr::lng_feature_custom_background_channel)(proj),
  170. st::boostFeatureCustomBackground);
  171. }
  172. if (i >= features.wallpaperLevel) {
  173. add(
  174. (group
  175. ? tr::lng_feature_backgrounds_group
  176. : tr::lng_feature_backgrounds_channel)(
  177. lt_count,
  178. rpl::single(float64(features.wallpapersCount)),
  179. proj),
  180. st::boostFeatureBackground);
  181. }
  182. if (i >= features.emojiStatusLevel) {
  183. add(
  184. tr::lng_feature_emoji_status(proj),
  185. st::boostFeatureEmojiStatus);
  186. }
  187. if (group && i >= features.transcribeLevel) {
  188. add(
  189. tr::lng_feature_transcribe(proj),
  190. st::boostFeatureTranscribe);
  191. }
  192. if (group && i >= features.emojiPackLevel) {
  193. add(
  194. tr::lng_feature_custom_emoji_pack(proj),
  195. st::boostFeatureCustomEmoji);
  196. }
  197. if (!group) {
  198. if (const auto j = features.linkStylesByLevel.find(i)
  199. ; j != end(features.linkStylesByLevel)) {
  200. linkStyles += j->second;
  201. }
  202. if (i >= features.linkLogoLevel) {
  203. add(
  204. tr::lng_feature_link_emoji(proj),
  205. st::boostFeatureCustomLink);
  206. }
  207. if (linkStyles > 0) {
  208. add(tr::lng_feature_link_style_channel(
  209. lt_count,
  210. rpl::single(float64(linkStyles)),
  211. proj
  212. ), st::boostFeatureLink);
  213. }
  214. if (const auto j = features.nameColorsByLevel.find(i)
  215. ; j != end(features.nameColorsByLevel)) {
  216. nameColors += j->second;
  217. }
  218. if (nameColors > 0) {
  219. add(tr::lng_feature_name_color_channel(
  220. lt_count,
  221. rpl::single(float64(nameColors)),
  222. proj
  223. ), st::boostFeatureName);
  224. }
  225. add(tr::lng_feature_reactions(
  226. lt_count,
  227. rpl::single(float64(i)),
  228. proj
  229. ), st::boostFeatureCustomReactions);
  230. }
  231. add(
  232. tr::lng_feature_stories(lt_count, rpl::single(float64(i)), proj),
  233. st::boostFeatureStories);
  234. }
  235. }
  236. } // namespace
  237. void StartFireworks(not_null<QWidget*> parent) {
  238. const auto result = Ui::CreateChild<RpWidget>(parent.get());
  239. result->setAttribute(Qt::WA_TransparentForMouseEvents);
  240. result->setGeometry(parent->rect());
  241. result->show();
  242. auto &lifetime = result->lifetime();
  243. const auto animation = lifetime.make_state<FireworksAnimation>([=] {
  244. result->update();
  245. });
  246. result->paintRequest() | rpl::start_with_next([=] {
  247. auto p = QPainter(result);
  248. if (!animation->paint(p, result->rect())) {
  249. crl::on_main(result, [=] { delete result; });
  250. }
  251. }, lifetime);
  252. }
  253. void BoostBox(
  254. not_null<GenericBox*> box,
  255. BoostBoxData data,
  256. Fn<void(Fn<void(BoostCounters)>)> boost) {
  257. box->setWidth(st::boxWideWidth);
  258. box->setStyle(st::boostBox);
  259. //AssertIsDebug();
  260. //data.boost = {
  261. // .level = 2,
  262. // .boosts = 3,
  263. // .thisLevelBoosts = 2,
  264. // .nextLevelBoosts = 5,
  265. // .mine = 2,
  266. //};
  267. struct State {
  268. rpl::variable<BoostCounters> data;
  269. rpl::variable<bool> full;
  270. bool submitted = false;
  271. };
  272. const auto state = box->lifetime().make_state<State>();
  273. state->data = std::move(data.boost);
  274. FillBoostLimit(
  275. BoxShowFinishes(box),
  276. box->verticalLayout(),
  277. state->data.value(),
  278. st::boxRowPadding);
  279. box->setMaxHeight(st::boostBoxMaxHeight);
  280. const auto close = box->addTopButton(
  281. st::boxTitleClose,
  282. [=] { box->closeBox(); });
  283. const auto name = data.name;
  284. auto title = state->data.value(
  285. ) | rpl::map([=](BoostCounters counters) {
  286. return (counters.mine > 0)
  287. ? tr::lng_boost_channel_you_title(
  288. lt_channel,
  289. rpl::single(name))
  290. : !counters.nextLevelBoosts
  291. ? tr::lng_boost_channel_title_max()
  292. : counters.level
  293. ? (data.group
  294. ? tr::lng_boost_channel_title_more_group()
  295. : tr::lng_boost_channel_title_more())
  296. : (data.group
  297. ? tr::lng_boost_channel_title_first_group()
  298. : tr::lng_boost_channel_title_first());
  299. }) | rpl::flatten_latest();
  300. auto repeated = state->data.value(
  301. ) | rpl::map([=](BoostCounters counters) {
  302. return (counters.mine > 1) ? u"x%1"_q.arg(counters.mine) : u""_q;
  303. });
  304. const auto wasMine = state->data.current().mine;
  305. const auto wasLifting = data.lifting;
  306. auto text = state->data.value(
  307. ) | rpl::map([=](BoostCounters counters) {
  308. const auto lifting = wasLifting
  309. ? (wasLifting
  310. - std::clamp(counters.mine - wasMine, 0, wasLifting - 1))
  311. : 0;
  312. const auto bold = Ui::Text::Bold(name);
  313. const auto now = counters.boosts;
  314. const auto full = !counters.nextLevelBoosts;
  315. const auto left = (counters.nextLevelBoosts > now)
  316. ? (counters.nextLevelBoosts - now)
  317. : 0;
  318. auto post = tr::lng_boost_channel_post_stories(
  319. lt_count,
  320. rpl::single(float64(counters.level + (left ? 1 : 0))),
  321. Ui::Text::RichLangValue);
  322. return (lifting > 1)
  323. ? tr::lng_boost_group_lift_restrictions_many(
  324. lt_count,
  325. rpl::single(float64(lifting)),
  326. Ui::Text::RichLangValue)
  327. : lifting
  328. ? tr::lng_boost_group_lift_restrictions(Ui::Text::RichLangValue)
  329. : (counters.mine || full)
  330. ? (left
  331. ? tr::lng_boost_channel_needs_unlock(
  332. lt_count,
  333. rpl::single(float64(left)),
  334. lt_channel,
  335. rpl::single(bold),
  336. Ui::Text::RichLangValue)
  337. : (!counters.level
  338. ? (data.group
  339. ? tr::lng_boost_channel_reached_first_group
  340. : tr::lng_boost_channel_reached_first)(
  341. Ui::Text::RichLangValue)
  342. : (data.group
  343. ? tr::lng_boost_channel_reached_more_group
  344. : tr::lng_boost_channel_reached_more)(
  345. lt_count,
  346. rpl::single(float64(counters.level)),
  347. lt_post,
  348. std::move(post),
  349. Ui::Text::RichLangValue)))
  350. : tr::lng_boost_channel_needs_unlock(
  351. lt_count,
  352. rpl::single(float64(left)),
  353. lt_channel,
  354. rpl::single(bold),
  355. Ui::Text::RichLangValue);
  356. }) | rpl::flatten_latest();
  357. if (wasLifting) {
  358. state->data.value(
  359. ) | rpl::start_with_next([=](BoostCounters counters) {
  360. if (counters.mine - wasMine >= wasLifting) {
  361. box->closeBox();
  362. }
  363. }, box->lifetime());
  364. }
  365. auto faded = object_ptr<Ui::FadeWrap<>>(
  366. close->parentWidget(),
  367. MakeTitle(
  368. box,
  369. (data.group
  370. ? tr::lng_boost_group_button
  371. : tr::lng_boost_channel_button)(),
  372. rpl::duplicate(repeated),
  373. false));
  374. const auto titleInner = faded.data();
  375. titleInner->move(st::boxTitlePosition);
  376. titleInner->resizeToWidth(st::boxWideWidth
  377. - st::boxTitleClose.width
  378. - st::boxTitlePosition.x());
  379. titleInner->hide(anim::type::instant);
  380. crl::on_main(titleInner, [=] {
  381. titleInner->raise();
  382. titleInner->toggleOn(rpl::single(
  383. rpl::empty
  384. ) | rpl::then(
  385. box->scrolls()
  386. ) | rpl::map([=] {
  387. return box->scrollTop() > 0;
  388. }));
  389. });
  390. box->addRow(
  391. MakeTitle(box, std::move(title), std::move(repeated)),
  392. st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0));
  393. box->addRow(
  394. object_ptr<Ui::FlatLabel>(
  395. box,
  396. std::move(text),
  397. st::boostText),
  398. (st::boxRowPadding
  399. + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip)));
  400. const auto current = state->data.current();
  401. box->setTitle(rpl::single(QString()));
  402. AddFeaturesList(
  403. box->verticalLayout(),
  404. data.features,
  405. current.level + (current.nextLevelBoosts ? 1 : 0),
  406. data.group);
  407. const auto allowMulti = data.allowMulti;
  408. auto submit = state->data.value(
  409. ) | rpl::map([=](BoostCounters counters) {
  410. return (!counters.nextLevelBoosts || (counters.mine && !allowMulti))
  411. ? tr::lng_box_ok()
  412. : (counters.mine > 0)
  413. ? tr::lng_boost_again_button()
  414. : data.group
  415. ? tr::lng_boost_group_button()
  416. : tr::lng_boost_channel_button();
  417. }) | rpl::flatten_latest();
  418. const auto button = box->addButton(rpl::duplicate(submit), [=] {
  419. if (state->submitted) {
  420. return;
  421. } else if (state->data.current().nextLevelBoosts > 0
  422. && (allowMulti || !state->data.current().mine)) {
  423. state->submitted = true;
  424. const auto was = state->data.current().mine;
  425. //AssertIsDebug();
  426. //state->submitted = false;
  427. //if (state->data.current().level == 5
  428. // && state->data.current().boosts == 11) {
  429. // state->data = BoostCounters{
  430. // .level = 5,
  431. // .boosts = 14,
  432. // .thisLevelBoosts = 9,
  433. // .nextLevelBoosts = 15,
  434. // .mine = 14,
  435. // };
  436. //} else if (state->data.current().level == 5) {
  437. // state->data = BoostCounters{
  438. // .level = 7,
  439. // .boosts = 16,
  440. // .thisLevelBoosts = 15,
  441. // .nextLevelBoosts = 19,
  442. // .mine = 16,
  443. // };
  444. //} else if (state->data.current().level == 4) {
  445. // state->data = BoostCounters{
  446. // .level = 5,
  447. // .boosts = 11,
  448. // .thisLevelBoosts = 9,
  449. // .nextLevelBoosts = 15,
  450. // .mine = 9,
  451. // };
  452. //} else if (state->data.current().level == 3) {
  453. // state->data = BoostCounters{
  454. // .level = 4,
  455. // .boosts = 7,
  456. // .thisLevelBoosts = 7,
  457. // .nextLevelBoosts = 9,
  458. // .mine = 5,
  459. // };
  460. //} else {
  461. // state->data = BoostCounters{
  462. // .level = 3,
  463. // .boosts = 5,
  464. // .thisLevelBoosts = 5,
  465. // .nextLevelBoosts = 7,
  466. // .mine = 3,
  467. // };
  468. //}
  469. //return;
  470. boost(crl::guard(box, [=](BoostCounters result) {
  471. state->submitted = false;
  472. if (result.thisLevelBoosts || result.nextLevelBoosts) {
  473. if (result.mine > was) {
  474. StartFireworks(box->parentWidget());
  475. }
  476. state->data = result;
  477. }
  478. }));
  479. } else {
  480. box->closeBox();
  481. }
  482. });
  483. rpl::combine(
  484. std::move(submit),
  485. box->widthValue()
  486. ) | rpl::start_with_next([=](const QString &, int width) {
  487. const auto &padding = st::boostBox.buttonPadding;
  488. button->resizeToWidth(width
  489. - padding.left()
  490. - padding.right());
  491. button->moveToLeft(padding.left(), button->y());
  492. }, button->lifetime());
  493. }
  494. object_ptr<Ui::RpWidget> MakeLinkLabel(
  495. not_null<QWidget*> parent,
  496. rpl::producer<QString> text,
  497. rpl::producer<QString> link,
  498. std::shared_ptr<Ui::Show> show,
  499. object_ptr<Ui::RpWidget> right) {
  500. auto result = object_ptr<Ui::AbstractButton>(parent);
  501. const auto raw = result.data();
  502. const auto rawRight = right.release();
  503. if (rawRight) {
  504. rawRight->setParent(raw);
  505. rawRight->show();
  506. }
  507. struct State {
  508. State(
  509. not_null<QWidget*> parent,
  510. rpl::producer<QString> value,
  511. rpl::producer<QString> link)
  512. : text(std::move(value))
  513. , link(std::move(link))
  514. , label(parent, text.value(), st::giveawayGiftCodeLink)
  515. , bg(st::roundRadiusLarge, st::windowBgOver) {
  516. }
  517. rpl::variable<QString> text;
  518. rpl::variable<QString> link;
  519. Ui::FlatLabel label;
  520. Ui::RoundRect bg;
  521. };
  522. const auto state = raw->lifetime().make_state<State>(
  523. raw,
  524. rpl::duplicate(text),
  525. std::move(link));
  526. state->label.setSelectable(true);
  527. rpl::combine(
  528. raw->widthValue(),
  529. std::move(text)
  530. ) | rpl::start_with_next([=](int outer, const auto&) {
  531. const auto textWidth = state->label.textMaxWidth();
  532. const auto skipLeft = st::giveawayGiftCodeLink.margin.left();
  533. const auto skipRight = rawRight
  534. ? rawRight->width()
  535. : st::giveawayGiftCodeLink.margin.right();
  536. const auto available = outer - skipRight - skipLeft;
  537. const auto use = std::min(textWidth, available);
  538. state->label.resizeToWidth(use);
  539. const auto forCenter = (outer - use) / 2;
  540. const auto x = (forCenter < skipLeft)
  541. ? skipLeft
  542. : (forCenter > outer - skipRight - use)
  543. ? (outer - skipRight - use)
  544. : forCenter;
  545. state->label.moveToLeft(x, st::giveawayGiftCodeLink.margin.top());
  546. }, raw->lifetime());
  547. raw->paintRequest() | rpl::start_with_next([=] {
  548. auto p = QPainter(raw);
  549. state->bg.paint(p, raw->rect());
  550. }, raw->lifetime());
  551. state->label.setAttribute(Qt::WA_TransparentForMouseEvents);
  552. raw->resize(raw->width(), st::giveawayGiftCodeLinkHeight);
  553. if (rawRight) {
  554. raw->widthValue() | rpl::start_with_next([=](int width) {
  555. rawRight->move(width - rawRight->width(), 0);
  556. }, raw->lifetime());
  557. }
  558. raw->setClickedCallback([=] {
  559. QGuiApplication::clipboard()->setText(state->link.current());
  560. show->showToast(tr::lng_username_copied(tr::now));
  561. });
  562. return result;
  563. }
  564. void BoostBoxAlready(not_null<GenericBox*> box, bool group) {
  565. ConfirmBox(box, {
  566. .text = (group
  567. ? tr::lng_boost_error_already_text_group
  568. : tr::lng_boost_error_already_text)(Text::RichLangValue),
  569. .title = tr::lng_boost_error_already_title(),
  570. .inform = true,
  571. });
  572. }
  573. void GiftForBoostsBox(
  574. not_null<GenericBox*> box,
  575. QString channel,
  576. int receive,
  577. bool again) {
  578. ConfirmBox(box, {
  579. .text = (again
  580. ? tr::lng_boost_need_more_again
  581. : tr::lng_boost_need_more_text)(
  582. lt_count,
  583. rpl::single(receive) | tr::to_count(),
  584. lt_channel,
  585. rpl::single(TextWithEntities{ channel }),
  586. Text::RichLangValue),
  587. .title = tr::lng_boost_need_more(),
  588. .inform = true,
  589. });
  590. }
  591. void GiftedNoBoostsBox(not_null<GenericBox*> box, bool group) {
  592. InformBox(box, {
  593. .text = (group
  594. ? tr::lng_boost_error_gifted_text_group
  595. : tr::lng_boost_error_gifted_text)(Text::RichLangValue),
  596. .title = tr::lng_boost_error_gifted_title(),
  597. });
  598. }
  599. void PremiumForBoostsBox(
  600. not_null<GenericBox*> box,
  601. bool group,
  602. Fn<void()> buyPremium) {
  603. ConfirmBox(box, {
  604. .text = (group
  605. ? tr::lng_boost_error_premium_text_group
  606. : tr::lng_boost_error_premium_text)(Text::RichLangValue),
  607. .confirmed = buyPremium,
  608. .confirmText = tr::lng_boost_error_premium_yes(),
  609. .title = tr::lng_boost_error_premium_title(),
  610. });
  611. }
  612. void AskBoostBox(
  613. not_null<GenericBox*> box,
  614. AskBoostBoxData data,
  615. Fn<void()> openStatistics,
  616. Fn<void()> startGiveaway) {
  617. box->setWidth(st::boxWideWidth);
  618. box->setStyle(st::boostBox);
  619. FillBoostLimit(
  620. BoxShowFinishes(box),
  621. box->verticalLayout(),
  622. rpl::single(data.boost),
  623. st::boxRowPadding);
  624. box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
  625. auto title = v::match(data.reason.data, [](AskBoostChannelColor) {
  626. return tr::lng_boost_channel_title_color();
  627. }, [](AskBoostWallpaper) {
  628. return tr::lng_boost_channel_title_wallpaper();
  629. }, [](AskBoostEmojiStatus) {
  630. return tr::lng_boost_channel_title_status();
  631. }, [](AskBoostEmojiPack) {
  632. return tr::lng_boost_group_title_emoji();
  633. }, [](AskBoostCustomReactions) {
  634. return tr::lng_boost_channel_title_reactions();
  635. }, [](AskBoostCpm) {
  636. return tr::lng_boost_channel_title_cpm();
  637. }, [](AskBoostWearCollectible) {
  638. return tr::lng_boost_channel_title_wear();
  639. });
  640. auto isGroup = false;
  641. auto reasonText = v::match(data.reason.data, [&](
  642. AskBoostChannelColor data) {
  643. return tr::lng_boost_channel_needs_level_color(
  644. lt_count,
  645. rpl::single(float64(data.requiredLevel)),
  646. Ui::Text::RichLangValue);
  647. }, [&](AskBoostWallpaper data) {
  648. isGroup = data.group;
  649. return (data.group
  650. ? tr::lng_boost_group_needs_level_wallpaper
  651. : tr::lng_boost_channel_needs_level_wallpaper)(
  652. lt_count,
  653. rpl::single(float64(data.requiredLevel)),
  654. Ui::Text::RichLangValue);
  655. }, [&](AskBoostEmojiStatus data) {
  656. isGroup = data.group;
  657. return (data.group
  658. ? tr::lng_boost_group_needs_level_status
  659. : tr::lng_boost_channel_needs_level_status)(
  660. lt_count,
  661. rpl::single(float64(data.requiredLevel)),
  662. Ui::Text::RichLangValue);
  663. }, [&](AskBoostEmojiPack data) {
  664. isGroup = true;
  665. return tr::lng_boost_group_needs_level_emoji(
  666. lt_count,
  667. rpl::single(float64(data.requiredLevel)),
  668. Ui::Text::RichLangValue);
  669. }, [&](AskBoostCustomReactions data) {
  670. return tr::lng_boost_channel_needs_level_reactions(
  671. lt_count,
  672. rpl::single(float64(data.count)),
  673. lt_same_count,
  674. rpl::single(TextWithEntities{ QString::number(data.count) }),
  675. Ui::Text::RichLangValue);
  676. }, [&](AskBoostCpm data) {
  677. return tr::lng_boost_channel_needs_level_cpm(
  678. lt_count,
  679. rpl::single(float64(data.requiredLevel)),
  680. Ui::Text::RichLangValue);
  681. }, [&](AskBoostWearCollectible data) {
  682. return tr::lng_boost_channel_needs_level_wear(
  683. lt_count,
  684. rpl::single(float64(data.requiredLevel)),
  685. Ui::Text::RichLangValue);
  686. });
  687. auto text = rpl::combine(
  688. std::move(reasonText),
  689. (isGroup ? tr::lng_boost_group_ask : tr::lng_boost_channel_ask)(
  690. Ui::Text::RichLangValue)
  691. ) | rpl::map([](TextWithEntities &&text, TextWithEntities &&ask) {
  692. return text.append(u"\n\n"_q).append(std::move(ask));
  693. });
  694. box->addRow(
  695. object_ptr<Ui::FlatLabel>(
  696. box,
  697. std::move(title),
  698. st::boostCenteredTitle),
  699. st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0));
  700. box->addRow(
  701. object_ptr<Ui::FlatLabel>(
  702. box,
  703. std::move(text),
  704. st::boostText),
  705. (st::boxRowPadding
  706. + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip)));
  707. auto stats = object_ptr<Ui::IconButton>(box, st::boostLinkStatsButton);
  708. stats->setClickedCallback(openStatistics);
  709. box->addRow(MakeLinkLabel(
  710. box,
  711. rpl::single(data.link),
  712. rpl::single(data.link),
  713. box->uiShow(),
  714. std::move(stats)));
  715. auto submit = tr::lng_boost_channel_ask_button();
  716. const auto button = box->addButton(rpl::duplicate(submit), [=] {
  717. QGuiApplication::clipboard()->setText(data.link);
  718. box->uiShow()->showToast(tr::lng_username_copied(tr::now));
  719. });
  720. rpl::combine(
  721. std::move(submit),
  722. box->widthValue()
  723. ) | rpl::start_with_next([=](const QString &, int width) {
  724. const auto &padding = st::boostBox.buttonPadding;
  725. button->resizeToWidth(width
  726. - padding.left()
  727. - padding.right());
  728. button->moveToLeft(padding.left(), button->y());
  729. }, button->lifetime());
  730. }
  731. void FillBoostLimit(
  732. rpl::producer<> showFinished,
  733. not_null<VerticalLayout*> container,
  734. rpl::producer<BoostCounters> data,
  735. style::margins limitLinePadding) {
  736. const auto addSkip = [&](int skip) {
  737. container->add(object_ptr<Ui::FixedHeightWidget>(container, skip));
  738. };
  739. const auto ratio = [=](BoostCounters counters) {
  740. const auto min = counters.thisLevelBoosts;
  741. const auto max = counters.nextLevelBoosts;
  742. Assert(counters.boosts >= min && counters.boosts <= max);
  743. const auto count = (max - min);
  744. const auto index = (counters.boosts - min);
  745. if (!index) {
  746. return 0.;
  747. } else if (index == count) {
  748. return 1.;
  749. } else if (count == 2) {
  750. return 0.5;
  751. }
  752. const auto available = st::boxWideWidth
  753. - st::boxPadding.left()
  754. - st::boxPadding.right();
  755. const auto average = available / float64(count);
  756. const auto levelWidth = [&](int add) {
  757. return st::normalFont->width(
  758. tr::lng_boost_level(
  759. tr::now,
  760. lt_count,
  761. counters.level + add));
  762. };
  763. const auto paddings = 2 * st::premiumLineTextSkip;
  764. const auto labelLeftWidth = paddings + levelWidth(0);
  765. const auto labelRightWidth = paddings + levelWidth(1);
  766. const auto first = std::max(average, labelLeftWidth * 1.);
  767. const auto last = std::max(average, labelRightWidth * 1.);
  768. const auto other = (available - first - last) / (count - 2);
  769. return (first + (index - 1) * other) / available;
  770. };
  771. auto adjustedData = rpl::duplicate(data) | rpl::map(AdjustByReached);
  772. auto bubbleRowState = rpl::duplicate(
  773. adjustedData
  774. ) | rpl::combine_previous(
  775. BoostCounters()
  776. ) | rpl::map([=](BoostCounters previous, BoostCounters counters) {
  777. return Premium::BubbleRowState{
  778. .counter = counters.boosts,
  779. .ratio = ratio(counters),
  780. .animateFromZero = (counters.level != previous.level),
  781. .dynamic = true,
  782. };
  783. });
  784. Premium::AddBubbleRow(
  785. container,
  786. st::boostBubble,
  787. std::move(showFinished),
  788. rpl::duplicate(bubbleRowState),
  789. Premium::BubbleType::Premium,
  790. nullptr,
  791. &st::premiumIconBoost,
  792. limitLinePadding);
  793. addSkip(st::premiumLineTextSkip);
  794. const auto level = [](int level) {
  795. return tr::lng_boost_level(tr::now, lt_count, level);
  796. };
  797. auto limitState = std::move(
  798. bubbleRowState
  799. ) | rpl::map([](const Premium::BubbleRowState &state) {
  800. return Premium::LimitRowState{
  801. .ratio = state.ratio,
  802. .animateFromZero = state.animateFromZero,
  803. .dynamic = state.dynamic
  804. };
  805. });
  806. auto left = rpl::duplicate(
  807. adjustedData
  808. ) | rpl::map([=](BoostCounters counters) {
  809. return level(counters.level);
  810. });
  811. auto right = rpl::duplicate(
  812. adjustedData
  813. ) | rpl::map([=](BoostCounters counters) {
  814. return level(counters.level + 1);
  815. });
  816. Premium::AddLimitRow(
  817. container,
  818. st::boostLimits,
  819. Premium::LimitRowLabels{
  820. .leftLabel = std::move(left),
  821. .rightLabel = std::move(right),
  822. },
  823. std::move(limitState),
  824. limitLinePadding);
  825. }
  826. object_ptr<Ui::FlatLabel> MakeBoostFeaturesBadge(
  827. not_null<QWidget*> parent,
  828. rpl::producer<QString> text,
  829. Fn<QBrush(QRect)> bg) {
  830. auto result = object_ptr<Ui::FlatLabel>(
  831. parent,
  832. std::move(text),
  833. st::boostLevelBadge);
  834. const auto label = result.data();
  835. label->show();
  836. label->paintRequest() | rpl::start_with_next([=] {
  837. const auto size = label->textMaxWidth();
  838. const auto rect = QRect(
  839. (label->width() - size) / 2,
  840. st::boostLevelBadge.margin.top(),
  841. size,
  842. st::boostLevelBadge.style.font->height
  843. ).marginsAdded(st::boostLevelBadge.margin);
  844. auto p = QPainter(label);
  845. auto hq = PainterHighQualityEnabler(p);
  846. p.setBrush(bg(rect));
  847. p.setPen(Qt::NoPen);
  848. p.drawRoundedRect(rect, rect.height() / 2., rect.height() / 2.);
  849. const auto &lineFg = st::windowBgRipple;
  850. const auto line = st::boostLevelBadgeLine;
  851. const auto top = st::boostLevelBadge.margin.top()
  852. + ((st::boostLevelBadge.style.font->height - line) / 2);
  853. const auto left = 0;
  854. const auto skip = st::boostLevelBadgeSkip;
  855. if (const auto right = rect.x() - skip; right > left) {
  856. p.fillRect(left, top, right - left, line, lineFg);
  857. }
  858. const auto right = label->width();
  859. if (const auto left = rect.x() + rect.width() + skip
  860. ; left < right) {
  861. p.fillRect(left, top, right - left, line, lineFg);
  862. }
  863. }, label->lifetime());
  864. return result;
  865. }
  866. } // namespace Ui