settings_premium.cpp 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797
  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_premium.h"
  8. #include "boxes/premium_preview_box.h"
  9. #include "boxes/sticker_set_box.h"
  10. #include "chat_helpers/stickers_lottie.h" // LottiePlayerFromDocument.
  11. #include "core/application.h"
  12. #include "core/click_handler_types.h"
  13. #include "core/local_url_handlers.h" // Core::TryConvertUrlToLocal.
  14. #include "core/ui_integration.h" // TextContext.
  15. #include "data/data_document.h"
  16. #include "data/data_document_media.h"
  17. #include "data/data_emoji_statuses.h"
  18. #include "data/data_peer_values.h"
  19. #include "data/data_session.h"
  20. #include "data/stickers/data_custom_emoji.h" // SerializeCustomEmojiId.
  21. #include "data/stickers/data_stickers.h"
  22. #include "history/view/media/history_view_sticker.h" // EmojiSize.
  23. #include "history/view/media/history_view_sticker_player.h"
  24. #include "info/info_wrap_widget.h" // Info::Wrap.
  25. #include "info/profile/info_profile_values.h"
  26. #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
  27. #include "lang/lang_keys.h"
  28. #include "main/main_app_config.h"
  29. #include "main/main_session.h"
  30. #include "settings/settings_common_session.h"
  31. #include "ui/abstract_button.h"
  32. #include "ui/basic_click_handlers.h"
  33. #include "ui/effects/gradient.h"
  34. #include "ui/effects/premium_graphics.h"
  35. #include "ui/effects/premium_stars_colored.h"
  36. #include "ui/effects/premium_top_bar.h"
  37. #include "ui/layers/generic_box.h"
  38. #include "ui/text/format_values.h"
  39. #include "ui/text/text_utilities.h"
  40. #include "ui/toast/toast.h"
  41. #include "ui/widgets/checkbox.h" // Ui::RadiobuttonGroup.
  42. #include "ui/widgets/gradient_round_button.h"
  43. #include "ui/widgets/labels.h"
  44. #include "ui/wrap/fade_wrap.h"
  45. #include "ui/wrap/padding_wrap.h"
  46. #include "ui/wrap/slide_wrap.h"
  47. #include "ui/wrap/vertical_layout.h"
  48. #include "ui/new_badges.h"
  49. #include "ui/painter.h"
  50. #include "ui/power_saving.h"
  51. #include "ui/vertical_list.h"
  52. #include "window/window_controller.h"
  53. #include "window/window_session_controller.h"
  54. #include "window/window_session_controller_link_info.h"
  55. #include "base/unixtime.h"
  56. #include "apiwrap.h"
  57. #include "api/api_premium.h"
  58. #include "styles/style_chat_helpers.h"
  59. #include "styles/style_premium.h"
  60. #include "styles/style_info.h"
  61. #include "styles/style_layers.h"
  62. #include "styles/style_settings.h"
  63. namespace Settings {
  64. namespace {
  65. using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData;
  66. [[nodiscard]] Data::PremiumSubscriptionOptions SubscriptionOptionsForRows(
  67. Data::PremiumSubscriptionOptions result) {
  68. for (auto &option : result) {
  69. const auto total = option.costTotal;
  70. const auto perMonth = option.costPerMonth;
  71. option.costTotal = tr::lng_premium_gift_per(
  72. tr::now,
  73. lt_cost,
  74. perMonth);
  75. option.costPerMonth = tr::lng_premium_subscribe_total(
  76. tr::now,
  77. lt_cost,
  78. total);
  79. if (option.duration == tr::lng_months(tr::now, lt_count, 1)) {
  80. option.costPerMonth = QString();
  81. option.duration = tr::lng_premium_subscribe_months_1(tr::now);
  82. } else if (option.duration == tr::lng_months(tr::now, lt_count, 6)) {
  83. option.duration = tr::lng_premium_subscribe_months_6(tr::now);
  84. } else if (option.duration == tr::lng_years(tr::now, lt_count, 1)) {
  85. option.duration = tr::lng_premium_subscribe_months_12(tr::now);
  86. }
  87. }
  88. return result;
  89. }
  90. [[nodiscard]] int TopTransitionSkip() {
  91. return (st::settingsButton.padding.top()
  92. + st::settingsPremiumRowTitlePadding.top()) / 2;
  93. }
  94. namespace Ref {
  95. namespace Gift {
  96. struct Data {
  97. PeerId peerId;
  98. int months = 0;
  99. bool me = false;
  100. explicit operator bool() const {
  101. return peerId != 0;
  102. }
  103. };
  104. [[nodiscard]] QString Serialize(const Data &gift) {
  105. return QString::number(gift.peerId.value)
  106. + ':'
  107. + QString::number(gift.months)
  108. + ':'
  109. + QString::number(gift.me ? 1 : 0);
  110. }
  111. [[nodiscard]] Data Parse(QStringView data) {
  112. const auto components = data.split(':');
  113. if (components.size() != 3) {
  114. return {};
  115. }
  116. return {
  117. .peerId = PeerId(components[0].toULongLong()),
  118. .months = components[1].toInt(),
  119. .me = (components[2].toInt() == 1),
  120. };
  121. }
  122. } // namespace Gift
  123. namespace EmojiStatus {
  124. struct Data {
  125. PeerId peerId;
  126. explicit operator bool() const {
  127. return peerId != 0;
  128. }
  129. };
  130. [[nodiscard]] QString Serialize(const Data &gift) {
  131. return QString("profile_:%1").arg(QString::number(gift.peerId.value));
  132. }
  133. [[nodiscard]] Data Parse(QStringView data) {
  134. if (data.startsWith(u"profile_:"_q)) {
  135. const auto components = data.split(':');
  136. if (components.size() != 2) {
  137. return {};
  138. }
  139. return {
  140. .peerId = PeerId(components[1].toULongLong()),
  141. };
  142. }
  143. return {};
  144. }
  145. } // namespace EmojiStatus
  146. } // namespace Ref
  147. struct Entry {
  148. const style::icon *icon;
  149. rpl::producer<QString> title;
  150. rpl::producer<QString> description;
  151. PremiumFeature section = PremiumFeature::DoubleLimits;
  152. bool newBadge = false;
  153. };
  154. using Order = std::vector<QString>;
  155. [[nodiscard]] Order FallbackOrder() {
  156. return Order{
  157. u"stories"_q,
  158. u"more_upload"_q,
  159. u"double_limits"_q,
  160. u"last_seen"_q,
  161. u"voice_to_text"_q,
  162. u"faster_download"_q,
  163. u"translations"_q,
  164. u"animated_emoji"_q,
  165. u"emoji_status"_q,
  166. u"saved_tags"_q,
  167. //u"peer_colors"_q,
  168. u"wallpapers"_q,
  169. u"profile_badge"_q,
  170. u"message_privacy"_q,
  171. u"advanced_chat_management"_q,
  172. u"no_ads"_q,
  173. //u"app_icons"_q,
  174. u"infinite_reactions"_q,
  175. u"animated_userpics"_q,
  176. u"premium_stickers"_q,
  177. u"business"_q,
  178. u"effects"_q,
  179. };
  180. }
  181. [[nodiscard]] base::flat_map<QString, Entry> EntryMap() {
  182. return base::flat_map<QString, Entry>{
  183. {
  184. u"saved_tags"_q,
  185. Entry{
  186. &st::settingsPremiumIconTags,
  187. tr::lng_premium_summary_subtitle_tags_for_messages(),
  188. tr::lng_premium_summary_about_tags_for_messages(),
  189. PremiumFeature::TagsForMessages,
  190. },
  191. },
  192. {
  193. u"last_seen"_q,
  194. Entry{
  195. &st::settingsPremiumIconLastSeen,
  196. tr::lng_premium_summary_subtitle_last_seen(),
  197. tr::lng_premium_summary_about_last_seen(),
  198. PremiumFeature::LastSeen,
  199. },
  200. },
  201. {
  202. u"message_privacy"_q,
  203. Entry{
  204. &st::settingsPremiumIconPrivacy,
  205. tr::lng_premium_summary_subtitle_message_privacy(),
  206. tr::lng_premium_summary_about_message_privacy(),
  207. PremiumFeature::MessagePrivacy,
  208. },
  209. },
  210. {
  211. u"wallpapers"_q,
  212. Entry{
  213. &st::settingsPremiumIconWallpapers,
  214. tr::lng_premium_summary_subtitle_wallpapers(),
  215. tr::lng_premium_summary_about_wallpapers(),
  216. PremiumFeature::Wallpapers,
  217. },
  218. },
  219. {
  220. u"stories"_q,
  221. Entry{
  222. &st::settingsPremiumIconStories,
  223. tr::lng_premium_summary_subtitle_stories(),
  224. tr::lng_premium_summary_about_stories(),
  225. PremiumFeature::Stories,
  226. },
  227. },
  228. {
  229. u"double_limits"_q,
  230. Entry{
  231. &st::settingsPremiumIconDouble,
  232. tr::lng_premium_summary_subtitle_double_limits(),
  233. tr::lng_premium_summary_about_double_limits(),
  234. PremiumFeature::DoubleLimits,
  235. },
  236. },
  237. {
  238. u"more_upload"_q,
  239. Entry{
  240. &st::settingsPremiumIconFiles,
  241. tr::lng_premium_summary_subtitle_more_upload(),
  242. tr::lng_premium_summary_about_more_upload(),
  243. PremiumFeature::MoreUpload,
  244. },
  245. },
  246. {
  247. u"faster_download"_q,
  248. Entry{
  249. &st::settingsPremiumIconSpeed,
  250. tr::lng_premium_summary_subtitle_faster_download(),
  251. tr::lng_premium_summary_about_faster_download(),
  252. PremiumFeature::FasterDownload,
  253. },
  254. },
  255. {
  256. u"voice_to_text"_q,
  257. Entry{
  258. &st::settingsPremiumIconVoice,
  259. tr::lng_premium_summary_subtitle_voice_to_text(),
  260. tr::lng_premium_summary_about_voice_to_text(),
  261. PremiumFeature::VoiceToText,
  262. },
  263. },
  264. {
  265. u"no_ads"_q,
  266. Entry{
  267. &st::settingsPremiumIconChannelsOff,
  268. tr::lng_premium_summary_subtitle_no_ads(),
  269. tr::lng_premium_summary_about_no_ads(),
  270. PremiumFeature::NoAds,
  271. },
  272. },
  273. {
  274. u"emoji_status"_q,
  275. Entry{
  276. &st::settingsPremiumIconStatus,
  277. tr::lng_premium_summary_subtitle_emoji_status(),
  278. tr::lng_premium_summary_about_emoji_status(),
  279. PremiumFeature::EmojiStatus,
  280. },
  281. },
  282. {
  283. u"infinite_reactions"_q,
  284. Entry{
  285. &st::settingsPremiumIconLike,
  286. tr::lng_premium_summary_subtitle_infinite_reactions(),
  287. tr::lng_premium_summary_about_infinite_reactions(),
  288. PremiumFeature::InfiniteReactions,
  289. },
  290. },
  291. {
  292. u"premium_stickers"_q,
  293. Entry{
  294. &st::settingsIconStickers,
  295. tr::lng_premium_summary_subtitle_premium_stickers(),
  296. tr::lng_premium_summary_about_premium_stickers(),
  297. PremiumFeature::Stickers,
  298. },
  299. },
  300. {
  301. u"animated_emoji"_q,
  302. Entry{
  303. &st::settingsIconEmoji,
  304. tr::lng_premium_summary_subtitle_animated_emoji(),
  305. tr::lng_premium_summary_about_animated_emoji(),
  306. PremiumFeature::AnimatedEmoji,
  307. },
  308. },
  309. {
  310. u"advanced_chat_management"_q,
  311. Entry{
  312. &st::settingsIconChat,
  313. tr::lng_premium_summary_subtitle_advanced_chat_management(),
  314. tr::lng_premium_summary_about_advanced_chat_management(),
  315. PremiumFeature::AdvancedChatManagement,
  316. },
  317. },
  318. {
  319. u"profile_badge"_q,
  320. Entry{
  321. &st::settingsPremiumIconStar,
  322. tr::lng_premium_summary_subtitle_profile_badge(),
  323. tr::lng_premium_summary_about_profile_badge(),
  324. PremiumFeature::ProfileBadge,
  325. },
  326. },
  327. {
  328. u"animated_userpics"_q,
  329. Entry{
  330. &st::settingsPremiumIconPlay,
  331. tr::lng_premium_summary_subtitle_animated_userpics(),
  332. tr::lng_premium_summary_about_animated_userpics(),
  333. PremiumFeature::AnimatedUserpics,
  334. },
  335. },
  336. {
  337. u"translations"_q,
  338. Entry{
  339. &st::settingsPremiumIconTranslations,
  340. tr::lng_premium_summary_subtitle_translation(),
  341. tr::lng_premium_summary_about_translation(),
  342. PremiumFeature::RealTimeTranslation,
  343. },
  344. },
  345. {
  346. u"business"_q,
  347. Entry{
  348. &st::settingsPremiumIconBusiness,
  349. tr::lng_premium_summary_subtitle_business(),
  350. tr::lng_premium_summary_about_business(),
  351. PremiumFeature::Business,
  352. true,
  353. },
  354. },
  355. {
  356. u"effects"_q,
  357. Entry{
  358. &st::settingsPremiumIconEffects,
  359. tr::lng_premium_summary_subtitle_effects(),
  360. tr::lng_premium_summary_about_effects(),
  361. PremiumFeature::Effects,
  362. true,
  363. },
  364. },
  365. };
  366. }
  367. void SendAppLog(
  368. not_null<Main::Session*> session,
  369. const QString &type,
  370. const MTPJSONValue &data) {
  371. const auto now = double(base::unixtime::now())
  372. + (QTime::currentTime().msec() / 1000.);
  373. session->api().request(MTPhelp_SaveAppLog(
  374. MTP_vector<MTPInputAppEvent>(1, MTP_inputAppEvent(
  375. MTP_double(now),
  376. MTP_string(type),
  377. MTP_long(0),
  378. data
  379. ))
  380. )).send();
  381. }
  382. [[nodiscard]] QString ResolveRef(const QString &ref) {
  383. return ref.isEmpty() ? "settings" : ref;
  384. }
  385. void SendScreenShow(
  386. not_null<Window::SessionController*> controller,
  387. const std::vector<QString> &order,
  388. const QString &ref) {
  389. auto list = QVector<MTPJSONValue>();
  390. list.reserve(order.size());
  391. for (const auto &element : order) {
  392. list.push_back(MTP_jsonString(MTP_string(element)));
  393. }
  394. auto values = QVector<MTPJSONObjectValue>{
  395. MTP_jsonObjectValue(
  396. MTP_string("premium_promo_order"),
  397. MTP_jsonArray(MTP_vector<MTPJSONValue>(std::move(list)))),
  398. MTP_jsonObjectValue(
  399. MTP_string("source"),
  400. MTP_jsonString(MTP_string(ResolveRef(ref)))),
  401. };
  402. const auto data = MTP_jsonObject(
  403. MTP_vector<MTPJSONObjectValue>(std::move(values)));
  404. SendAppLog(
  405. &controller->session(),
  406. "premium.promo_screen_show",
  407. data);
  408. }
  409. void SendScreenAccept(not_null<Window::SessionController*> controller) {
  410. SendAppLog(
  411. &controller->session(),
  412. "premium.promo_screen_accept",
  413. MTP_jsonNull());
  414. }
  415. class EmojiStatusTopBar final {
  416. public:
  417. EmojiStatusTopBar(
  418. not_null<DocumentData*> document,
  419. Fn<void(QRect)> callback,
  420. QSizeF size);
  421. void setCenter(QPointF position);
  422. void setPaused(bool paused);
  423. void paint(QPainter &p);
  424. private:
  425. QRectF _rect;
  426. std::shared_ptr<Data::DocumentMedia> _media;
  427. std::unique_ptr<HistoryView::StickerPlayer> _player;
  428. bool _paused = false;
  429. rpl::lifetime _lifetime;
  430. };
  431. EmojiStatusTopBar::EmojiStatusTopBar(
  432. not_null<DocumentData*> document,
  433. Fn<void(QRect)> callback,
  434. QSizeF size)
  435. : _rect(QPointF(), size) {
  436. const auto sticker = document->sticker();
  437. Assert(sticker != nullptr);
  438. _media = document->createMediaView();
  439. _media->checkStickerLarge();
  440. _media->goodThumbnailWanted();
  441. rpl::single() | rpl::then(
  442. document->owner().session().downloaderTaskFinished()
  443. ) | rpl::start_with_next([=] {
  444. if (!_media->loaded()) {
  445. return;
  446. }
  447. _lifetime.destroy();
  448. if (sticker->isLottie()) {
  449. _player = std::make_unique<HistoryView::LottiePlayer>(
  450. ChatHelpers::LottiePlayerFromDocument(
  451. _media.get(),
  452. ChatHelpers::StickerLottieSize::EmojiInteractionReserved7, //
  453. size.toSize(),
  454. Lottie::Quality::High));
  455. } else if (sticker->isWebm()) {
  456. _player = std::make_unique<HistoryView::WebmPlayer>(
  457. _media->owner()->location(),
  458. _media->bytes(),
  459. size.toSize());
  460. } else if (sticker) {
  461. _player = std::make_unique<HistoryView::StaticStickerPlayer>(
  462. _media->owner()->location(),
  463. _media->bytes(),
  464. size.toSize());
  465. }
  466. if (_player) {
  467. _player->setRepaintCallback([=] { callback(_rect.toRect()); });
  468. } else {
  469. callback(_rect.toRect());
  470. }
  471. }, _lifetime);
  472. }
  473. void EmojiStatusTopBar::setCenter(QPointF position) {
  474. const auto size = _rect.size();
  475. const auto shift = QPointF(size.width() / 2., size.height() / 2.);
  476. _rect = QRectF(QPointF(position - shift), QPointF(position + shift));
  477. }
  478. void EmojiStatusTopBar::setPaused(bool paused) {
  479. _paused = paused;
  480. }
  481. void EmojiStatusTopBar::paint(QPainter &p) {
  482. if (_player && _player->ready()) {
  483. const auto frame = _player->frame(
  484. _rect.size().toSize(),
  485. (_media->owner()->emojiUsesTextColor()
  486. ? st::profileVerifiedCheckBg->c
  487. : QColor(0, 0, 0, 0)),
  488. false,
  489. crl::now(),
  490. _paused || On(PowerSaving::kEmojiStatus));
  491. p.drawImage(_rect.toRect(), frame.image);
  492. if (!_paused) {
  493. _player->markFrameShown();
  494. }
  495. }
  496. }
  497. class TopBarUser final : public Ui::Premium::TopBarAbstract {
  498. public:
  499. TopBarUser(
  500. not_null<QWidget*> parent,
  501. not_null<Window::SessionController*> controller,
  502. not_null<PeerData*> peer,
  503. rpl::producer<> showFinished);
  504. void setPaused(bool paused) override;
  505. void setTextPosition(int x, int y) override;
  506. rpl::producer<int> additionalHeight() const override;
  507. protected:
  508. void paintEvent(QPaintEvent *e) override;
  509. void resizeEvent(QResizeEvent *e) override;
  510. private:
  511. void updateTitle(
  512. DocumentData *document,
  513. TextWithEntities name,
  514. not_null<Window::SessionController*> controller);
  515. void updateAbout(DocumentData *document) const;
  516. object_ptr<Ui::RpWidget> _content;
  517. object_ptr<Ui::FlatLabel> _title;
  518. object_ptr<Ui::FlatLabel> _about;
  519. Ui::Premium::ColoredMiniStars _ministars;
  520. struct {
  521. object_ptr<Ui::RpWidget> widget;
  522. Ui::Text::String text;
  523. Ui::Animations::Simple animation;
  524. bool shown = false;
  525. QPoint position;
  526. } _smallTop;
  527. std::unique_ptr<EmojiStatusTopBar> _emojiStatus;
  528. QImage _imageStar;
  529. QRectF _starRect;
  530. };
  531. TopBarUser::TopBarUser(
  532. not_null<QWidget*> parent,
  533. not_null<Window::SessionController*> controller,
  534. not_null<PeerData*> peer,
  535. rpl::producer<> showFinished)
  536. : TopBarAbstract(parent, st::userPremiumCover)
  537. , _content(this)
  538. , _title(_content, st::settingsPremiumUserTitle)
  539. , _about(_content, st::userPremiumCover.about)
  540. , _ministars(_content, true)
  541. , _smallTop({
  542. .widget = object_ptr<Ui::RpWidget>(this),
  543. .text = Ui::Text::String(
  544. st::boxTitle.style,
  545. tr::lng_premium_summary_title(tr::now)),
  546. }) {
  547. _starRect = TopBarAbstract::starRect(1., 1.);
  548. rpl::single() | rpl::then(
  549. style::PaletteChanged()
  550. ) | rpl::start_with_next([=] {
  551. TopBarAbstract::computeIsDark();
  552. update();
  553. }, lifetime());
  554. auto documentValue = Info::Profile::EmojiStatusIdValue(
  555. peer
  556. ) | rpl::map([=](EmojiStatusId id) -> DocumentData* {
  557. const auto documentId = id.collectible
  558. ? id.collectible->documentId
  559. : id.documentId;
  560. const auto document = documentId
  561. ? controller->session().data().document(documentId).get()
  562. : nullptr;
  563. return (document && document->sticker()) ? document : nullptr;
  564. });
  565. rpl::combine(
  566. std::move(documentValue),
  567. Info::Profile::NameValue(peer)
  568. ) | rpl::start_with_next([=](
  569. DocumentData *document,
  570. const QString &name) {
  571. if (document) {
  572. _emojiStatus = std::make_unique<EmojiStatusTopBar>(
  573. document,
  574. [=](QRect r) { _content->update(std::move(r)); },
  575. HistoryView::Sticker::EmojiSize());
  576. _imageStar = QImage();
  577. } else {
  578. _emojiStatus = nullptr;
  579. _imageStar = Ui::Premium::GenerateStarForLightTopBar(_starRect);
  580. }
  581. updateTitle(document, { name }, controller);
  582. updateAbout(document);
  583. auto event = QResizeEvent(size(), size());
  584. resizeEvent(&event);
  585. update();
  586. }, lifetime());
  587. rpl::combine(
  588. _title->sizeValue(),
  589. _about->sizeValue(),
  590. _content->sizeValue()
  591. ) | rpl::start_with_next([=](
  592. const QSize &titleSize,
  593. const QSize &aboutSize,
  594. const QSize &size) {
  595. const auto rect = TopBarAbstract::starRect(1., 1.);
  596. const auto &padding = st::settingsPremiumUserTitlePadding;
  597. _title->moveToLeft(
  598. (size.width() - titleSize.width()) / 2,
  599. rect.top() + rect.height() + padding.top());
  600. _about->moveToLeft(
  601. (size.width() - aboutSize.width()) / 2,
  602. _title->y() + titleSize.height() + padding.bottom());
  603. const auto aboutBottom = _about->y() + _about->height();
  604. const auto height = (aboutBottom > st::settingsPremiumUserHeight)
  605. ? aboutBottom + padding.bottom()
  606. : st::settingsPremiumUserHeight;
  607. {
  608. const auto was = maximumHeight();
  609. const auto now = height;
  610. if (was != now) {
  611. setMaximumHeight(now);
  612. if (was == size.height()) {
  613. resize(size.width(), now);
  614. }
  615. }
  616. }
  617. _content->resize(size.width(), maximumHeight());
  618. }, lifetime());
  619. const auto smallTopShadow = Ui::CreateChild<Ui::FadeShadow>(
  620. _smallTop.widget.data());
  621. smallTopShadow->setDuration(st::infoTopBarDuration);
  622. rpl::combine(
  623. rpl::single(
  624. false
  625. ) | rpl::then(std::move(showFinished) | rpl::map_to(true)),
  626. sizeValue()
  627. ) | rpl::start_with_next([=](bool showFinished, const QSize &size) {
  628. _content->resize(size.width(), maximumHeight());
  629. const auto skip = TopTransitionSkip();
  630. _content->moveToLeft(0, size.height() - _content->height() - skip);
  631. _smallTop.widget->resize(size.width(), minimumHeight());
  632. smallTopShadow->resizeToWidth(size.width());
  633. smallTopShadow->moveToLeft(
  634. 0,
  635. _smallTop.widget->height() - smallTopShadow->height());
  636. const auto shown = (minimumHeight() * 2 > size.height());
  637. if (_smallTop.shown != shown) {
  638. _smallTop.shown = shown;
  639. if (!showFinished) {
  640. _smallTop.widget->update();
  641. smallTopShadow->toggle(_smallTop.shown, anim::type::instant);
  642. } else {
  643. _smallTop.animation.start(
  644. [=] { _smallTop.widget->update(); },
  645. _smallTop.shown ? 0. : 1.,
  646. _smallTop.shown ? 1. : 0.,
  647. st::infoTopBarDuration);
  648. smallTopShadow->toggle(_smallTop.shown, anim::type::normal);
  649. }
  650. }
  651. }, lifetime());
  652. _smallTop.widget->paintRequest(
  653. ) | rpl::start_with_next([=] {
  654. Painter p(_smallTop.widget);
  655. p.setOpacity(_smallTop.animation.value(_smallTop.shown ? 1. : 0.));
  656. TopBarAbstract::paintEdges(p);
  657. p.setPen(st::boxTitleFg);
  658. _smallTop.text.drawLeft(
  659. p,
  660. _smallTop.position.x(),
  661. _smallTop.position.y(),
  662. width(),
  663. width());
  664. }, lifetime());
  665. _content->paintRequest(
  666. ) | rpl::start_with_next([=] {
  667. auto p = QPainter(_content);
  668. _ministars.paint(p);
  669. if (_emojiStatus) {
  670. _emojiStatus->paint(p);
  671. } else if (!_imageStar.isNull()) {
  672. p.drawImage(_starRect.topLeft(), _imageStar);
  673. }
  674. }, lifetime());
  675. }
  676. void TopBarUser::updateTitle(
  677. DocumentData *document,
  678. TextWithEntities name,
  679. not_null<Window::SessionController*> controller) {
  680. if (!document) {
  681. return _title->setMarkedText(
  682. tr::lng_premium_summary_user_title(
  683. tr::now,
  684. lt_user,
  685. std::move(name),
  686. Ui::Text::WithEntities));
  687. }
  688. const auto stickerInfo = document->sticker();
  689. if (!stickerInfo) {
  690. return;
  691. }
  692. const auto owner = &document->owner();
  693. const auto &sets = owner->stickers().sets();
  694. const auto setId = stickerInfo->set.id;
  695. const auto it = sets.find(setId);
  696. if (it == sets.cend()) {
  697. return;
  698. }
  699. const auto set = it->second.get();
  700. const auto coloredId = owner->customEmojiManager().coloredSetId();
  701. const auto text = (set->thumbnailDocumentId ? QChar('0') : QChar())
  702. + set->title;
  703. const auto linkIndex = 1;
  704. const auto entityEmojiData = Data::SerializeCustomEmojiId(
  705. set->thumbnailDocumentId);
  706. const auto entities = EntitiesInText{
  707. { EntityType::CustomEmoji, 0, 1, entityEmojiData },
  708. Ui::Text::Link(text, linkIndex).entities.front(),
  709. };
  710. auto title = (setId == coloredId)
  711. ? tr::lng_premium_emoji_status_title_colored(
  712. tr::now,
  713. lt_user,
  714. std::move(name),
  715. Ui::Text::WithEntities)
  716. : tr::lng_premium_emoji_status_title(
  717. tr::now,
  718. lt_user,
  719. std::move(name),
  720. lt_link,
  721. { .text = text, .entities = entities, },
  722. Ui::Text::WithEntities);
  723. _title->setMarkedText(
  724. std::move(title),
  725. Core::TextContext({ .session = &controller->session() }));
  726. auto link = std::make_shared<LambdaClickHandler>([=,
  727. stickerSetIdentifier = stickerInfo->set] {
  728. setPaused(true);
  729. const auto box = controller->show(Box<StickerSetBox>(
  730. controller->uiShow(),
  731. stickerSetIdentifier,
  732. Data::StickersType::Emoji));
  733. box->boxClosing(
  734. ) | rpl::start_with_next(crl::guard(this, [=] {
  735. setPaused(false);
  736. }), box->lifetime());
  737. });
  738. _title->setLink(linkIndex, std::move(link));
  739. }
  740. void TopBarUser::updateAbout(DocumentData *document) const {
  741. _about->setMarkedText((document
  742. ? tr::lng_premium_emoji_status_about
  743. : tr::lng_premium_summary_user_about)(
  744. tr::now,
  745. Ui::Text::RichLangValue));
  746. }
  747. void TopBarUser::setPaused(bool paused) {
  748. _ministars.setPaused(paused);
  749. if (_emojiStatus) {
  750. _emojiStatus->setPaused(paused);
  751. }
  752. }
  753. void TopBarUser::setTextPosition(int x, int y) {
  754. _smallTop.position = { x, y };
  755. }
  756. rpl::producer<int> TopBarUser::additionalHeight() const {
  757. return rpl::never<int>();
  758. }
  759. void TopBarUser::paintEvent(QPaintEvent *e) {
  760. auto p = QPainter(this);
  761. TopBarAbstract::paintEdges(p);
  762. }
  763. void TopBarUser::resizeEvent(QResizeEvent *e) {
  764. _starRect = TopBarAbstract::starRect(1., 1.);
  765. _ministars.setCenter(_starRect.toRect());
  766. if (_emojiStatus) {
  767. _emojiStatus->setCenter(_starRect.center());
  768. }
  769. }
  770. class Premium : public Section<Premium> {
  771. public:
  772. Premium(
  773. QWidget *parent,
  774. not_null<Window::SessionController*> controller);
  775. [[nodiscard]] rpl::producer<QString> title() override;
  776. [[nodiscard]] QPointer<Ui::RpWidget> createPinnedToTop(
  777. not_null<QWidget*> parent) override;
  778. [[nodiscard]] QPointer<Ui::RpWidget> createPinnedToBottom(
  779. not_null<Ui::RpWidget*> parent) override;
  780. void showFinished() override;
  781. [[nodiscard]] bool hasFlexibleTopBar() const override;
  782. void setStepDataReference(std::any &data) override;
  783. [[nodiscard]] rpl::producer<> sectionShowBack() override final;
  784. private:
  785. void setupContent();
  786. void setupSubscriptionOptions(not_null<Ui::VerticalLayout*> container);
  787. const not_null<Window::SessionController*> _controller;
  788. const QString _ref;
  789. QPointer<Ui::GradientButton> _subscribe;
  790. base::unique_qptr<Ui::FadeWrap<Ui::IconButton>> _back;
  791. base::unique_qptr<Ui::IconButton> _close;
  792. rpl::variable<bool> _backToggles;
  793. rpl::variable<Info::Wrap> _wrap;
  794. Fn<void(bool)> _setPaused;
  795. std::shared_ptr<Ui::RadiobuttonGroup> _radioGroup;
  796. rpl::event_stream<> _showBack;
  797. rpl::event_stream<> _showFinished;
  798. rpl::variable<QString> _buttonText;
  799. };
  800. Premium::Premium(
  801. QWidget *parent,
  802. not_null<Window::SessionController*> controller)
  803. : Section(parent)
  804. , _controller(controller)
  805. , _ref(ResolveRef(controller->premiumRef()))
  806. , _radioGroup(std::make_shared<Ui::RadiobuttonGroup>()) {
  807. setupContent();
  808. _controller->session().api().premium().reload();
  809. }
  810. rpl::producer<QString> Premium::title() {
  811. return tr::lng_premium_summary_title();
  812. }
  813. bool Premium::hasFlexibleTopBar() const {
  814. return true;
  815. }
  816. rpl::producer<> Premium::sectionShowBack() {
  817. return _showBack.events();
  818. }
  819. void Premium::setStepDataReference(std::any &data) {
  820. const auto my = std::any_cast<SectionCustomTopBarData>(&data);
  821. if (my) {
  822. _backToggles = std::move(
  823. my->backButtonEnables
  824. ) | rpl::map_to(true);
  825. _wrap = std::move(my->wrapValue);
  826. }
  827. }
  828. void Premium::setupSubscriptionOptions(
  829. not_null<Ui::VerticalLayout*> container) {
  830. const auto isEmojiStatus = (!!Ref::EmojiStatus::Parse(_ref));
  831. const auto isGift = (!!Ref::Gift::Parse(_ref));
  832. const auto options = container->add(
  833. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  834. container,
  835. object_ptr<Ui::VerticalLayout>(container)));
  836. const auto skip = container->add(
  837. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  838. container,
  839. object_ptr<Ui::VerticalLayout>(container)));
  840. const auto content = options->entity();
  841. Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top());
  842. const auto apiPremium = &_controller->session().api().premium();
  843. Ui::Premium::AddGiftOptions(
  844. content,
  845. _radioGroup,
  846. SubscriptionOptionsForRows(apiPremium->subscriptionOptions()),
  847. st::premiumSubscriptionOption,
  848. true);
  849. Ui::AddSkip(content, st::settingsPremiumOptionsPadding.bottom());
  850. Ui::AddDivider(content);
  851. const auto lastSkip = TopTransitionSkip() * (isEmojiStatus ? 1 : 2);
  852. Ui::AddSkip(content, lastSkip - st::defaultVerticalListSkip);
  853. Ui::AddSkip(skip->entity(), lastSkip);
  854. if (isEmojiStatus || isGift) {
  855. options->toggle(false, anim::type::instant);
  856. skip->toggle(true, anim::type::instant);
  857. return;
  858. }
  859. auto toggleOn = rpl::combine(
  860. Data::AmPremiumValue(&_controller->session()),
  861. apiPremium->statusTextValue(
  862. ) | rpl::map([=] {
  863. return apiPremium->subscriptionOptions().size() < 2;
  864. })
  865. ) | rpl::map([=](bool premium, bool noOptions) {
  866. return !premium && !noOptions;
  867. });
  868. options->toggleOn(rpl::duplicate(toggleOn), anim::type::instant);
  869. skip->toggleOn(std::move(
  870. toggleOn
  871. ) | rpl::map([](bool value) { return !value; }), anim::type::instant);
  872. }
  873. void Premium::setupContent() {
  874. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  875. setupSubscriptionOptions(content);
  876. auto buttonCallback = [=](PremiumFeature section) {
  877. _setPaused(true);
  878. const auto hidden = crl::guard(this, [=] { _setPaused(false); });
  879. ShowPremiumPreviewToBuy(_controller, section, hidden);
  880. };
  881. AddSummaryPremium(content, _controller, _ref, std::move(buttonCallback));
  882. #if 0
  883. Ui::AddSkip(content);
  884. Ui::AddDivider(content);
  885. Ui::AddSkip(content);
  886. content->add(
  887. object_ptr<Ui::FlatLabel>(
  888. content,
  889. tr::lng_premium_summary_bottom_subtitle(
  890. ) | Ui::Text::ToBold(),
  891. stLabel),
  892. st::defaultSubsectionTitlePadding);
  893. content->add(
  894. object_ptr<Ui::FlatLabel>(
  895. content,
  896. tr::lng_premium_summary_bottom_about(Ui::Text::RichLangValue),
  897. st::aboutLabel),
  898. st::boxRowPadding);
  899. Ui::AddSkip(
  900. content,
  901. stDefault.padding.top() + stDefault.padding.bottom());
  902. #endif
  903. Ui::ResizeFitChild(this, content);
  904. }
  905. QPointer<Ui::RpWidget> Premium::createPinnedToTop(
  906. not_null<QWidget*> parent) {
  907. auto title = _controller->session().premium()
  908. ? tr::lng_premium_summary_title()
  909. : rpl::conditional(
  910. Data::AmPremiumValue(&_controller->session()),
  911. tr::lng_premium_summary_title_subscribed(),
  912. tr::lng_premium_summary_title());
  913. auto about = [&]() -> rpl::producer<TextWithEntities> {
  914. const auto gift = Ref::Gift::Parse(_ref);
  915. if (gift) {
  916. auto &data = _controller->session().data();
  917. if (const auto peer = data.peer(gift.peerId)) {
  918. return (gift.me
  919. ? tr::lng_premium_summary_subtitle_gift_me
  920. : tr::lng_premium_summary_subtitle_gift)(
  921. lt_count,
  922. rpl::single(float64(gift.months)),
  923. lt_user,
  924. rpl::single(Ui::Text::Bold(peer->name())),
  925. Ui::Text::RichLangValue);
  926. }
  927. }
  928. return rpl::conditional(
  929. Data::AmPremiumValue(&_controller->session()),
  930. _controller->session().api().premium().statusTextValue(),
  931. tr::lng_premium_summary_top_about(Ui::Text::RichLangValue));
  932. }();
  933. const auto emojiStatusData = Ref::EmojiStatus::Parse(_ref);
  934. const auto isEmojiStatus = (!!emojiStatusData);
  935. auto peerWithPremium = [&]() -> PeerData* {
  936. if (isEmojiStatus) {
  937. auto &data = _controller->session().data();
  938. if (const auto peer = data.peer(emojiStatusData.peerId)) {
  939. return peer;
  940. }
  941. }
  942. return nullptr;
  943. }();
  944. const auto content = [&]() -> Ui::Premium::TopBarAbstract* {
  945. if (peerWithPremium) {
  946. return Ui::CreateChild<TopBarUser>(
  947. parent.get(),
  948. _controller,
  949. peerWithPremium,
  950. _showFinished.events());
  951. }
  952. const auto weak = base::make_weak(_controller);
  953. const auto clickContextOther = [=] {
  954. return QVariant::fromValue(ClickHandlerContext{
  955. .sessionWindow = weak,
  956. .botStartAutoSubmit = true,
  957. });
  958. };
  959. return Ui::CreateChild<Ui::Premium::TopBar>(
  960. parent.get(),
  961. st::defaultPremiumCover,
  962. Ui::Premium::TopBarDescriptor{
  963. .clickContextOther = clickContextOther,
  964. .title = std::move(title),
  965. .about = std::move(about),
  966. });
  967. }();
  968. _setPaused = [=](bool paused) {
  969. content->setPaused(paused);
  970. if (_subscribe) {
  971. _subscribe->setGlarePaused(paused);
  972. }
  973. };
  974. _wrap.value(
  975. ) | rpl::start_with_next([=](Info::Wrap wrap) {
  976. content->setRoundEdges(wrap == Info::Wrap::Layer);
  977. }, content->lifetime());
  978. const auto calculateMaximumHeight = [=] {
  979. return isEmojiStatus
  980. ? st::settingsPremiumUserHeight + TopTransitionSkip()
  981. : st::settingsPremiumTopHeight;
  982. };
  983. content->setMaximumHeight(calculateMaximumHeight());
  984. content->setMinimumHeight(st::infoLayerTopBarHeight);
  985. content->resize(content->width(), content->maximumHeight());
  986. content->additionalHeight(
  987. ) | rpl::start_with_next([=](int additionalHeight) {
  988. const auto wasMax = (content->height() == content->maximumHeight());
  989. content->setMaximumHeight(calculateMaximumHeight()
  990. + additionalHeight);
  991. if (wasMax) {
  992. content->resize(content->width(), content->maximumHeight());
  993. }
  994. }, content->lifetime());
  995. _wrap.value(
  996. ) | rpl::start_with_next([=](Info::Wrap wrap) {
  997. const auto isLayer = (wrap == Info::Wrap::Layer);
  998. _back = base::make_unique_q<Ui::FadeWrap<Ui::IconButton>>(
  999. content,
  1000. object_ptr<Ui::IconButton>(
  1001. content,
  1002. isEmojiStatus
  1003. ? (isLayer ? st::infoTopBarBack : st::infoLayerTopBarBack)
  1004. : (isLayer
  1005. ? st::settingsPremiumLayerTopBarBack
  1006. : st::settingsPremiumTopBarBack)),
  1007. st::infoTopBarScale);
  1008. _back->setDuration(0);
  1009. _back->toggleOn(isLayer
  1010. ? _backToggles.value() | rpl::type_erased()
  1011. : rpl::single(true));
  1012. _back->entity()->addClickHandler([=] {
  1013. _showBack.fire({});
  1014. });
  1015. _back->toggledValue(
  1016. ) | rpl::start_with_next([=](bool toggled) {
  1017. const auto &st = isLayer ? st::infoLayerTopBar : st::infoTopBar;
  1018. content->setTextPosition(
  1019. toggled ? st.back.width : st.titlePosition.x(),
  1020. st.titlePosition.y());
  1021. }, _back->lifetime());
  1022. if (!isLayer) {
  1023. _close = nullptr;
  1024. } else {
  1025. _close = base::make_unique_q<Ui::IconButton>(
  1026. content,
  1027. isEmojiStatus
  1028. ? st::infoTopBarClose
  1029. : st::settingsPremiumTopBarClose);
  1030. _close->addClickHandler([=] {
  1031. _controller->parentController()->hideLayer();
  1032. _controller->parentController()->hideSpecialLayer();
  1033. });
  1034. content->widthValue(
  1035. ) | rpl::start_with_next([=] {
  1036. _close->moveToRight(0, 0);
  1037. }, _close->lifetime());
  1038. }
  1039. }, content->lifetime());
  1040. return Ui::MakeWeak(not_null<Ui::RpWidget*>{ content });
  1041. }
  1042. void Premium::showFinished() {
  1043. _showFinished.fire({});
  1044. }
  1045. QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
  1046. not_null<Ui::RpWidget*> parent) {
  1047. const auto content = Ui::CreateChild<Ui::RpWidget>(parent.get());
  1048. if (Ref::Gift::Parse(_ref)) {
  1049. return nullptr;
  1050. }
  1051. const auto emojiStatusData = Ref::EmojiStatus::Parse(_ref);
  1052. const auto session = &_controller->session();
  1053. auto buttonText = [&]() -> std::optional<rpl::producer<QString>> {
  1054. if (emojiStatusData) {
  1055. auto &data = session->data();
  1056. if (const auto peer = data.peer(emojiStatusData.peerId)) {
  1057. return Info::Profile::EmojiStatusIdValue(
  1058. peer
  1059. ) | rpl::map([=](EmojiStatusId id) {
  1060. return id
  1061. ? tr::lng_premium_emoji_status_button()
  1062. : _buttonText.value();
  1063. // : tr::lng_premium_summary_user_button();
  1064. }) | rpl::flatten_latest();
  1065. }
  1066. }
  1067. return _buttonText.value();
  1068. }();
  1069. _subscribe = CreateSubscribeButton({
  1070. _controller,
  1071. content,
  1072. [ref = _ref] { return ref; },
  1073. std::move(buttonText),
  1074. std::nullopt,
  1075. [=, options = session->api().premium().subscriptionOptions()] {
  1076. const auto value = _radioGroup->current();
  1077. return (value < options.size() && value >= 0)
  1078. ? options[value].botUrl
  1079. : QString();
  1080. },
  1081. });
  1082. #if 0
  1083. if (emojiStatusData) {
  1084. // "Learn More" should open the general Premium Settings
  1085. // so we override the button callback.
  1086. // To have ability to jump back to the User Premium Settings
  1087. // we should replace the ref explicitly.
  1088. _subscribe->setClickedCallback([=] {
  1089. const auto ref = _ref;
  1090. const auto controller = _controller;
  1091. ShowPremium(controller, QString());
  1092. controller->setPremiumRef(ref);
  1093. });
  1094. } else {
  1095. #endif
  1096. {
  1097. const auto callback = [=](int value) {
  1098. auto &api = _controller->session().api();
  1099. const auto options = api.premium().subscriptionOptions();
  1100. if (options.empty()) {
  1101. return;
  1102. }
  1103. Assert(value < options.size() && value >= 0);
  1104. auto text = tr::lng_premium_subscribe_button(
  1105. tr::now,
  1106. lt_cost,
  1107. options[value].costPerMonth);
  1108. _buttonText = std::move(text);
  1109. };
  1110. _radioGroup->setChangedCallback(callback);
  1111. callback(0);
  1112. }
  1113. _showFinished.events(
  1114. ) | rpl::take(1) | rpl::start_with_next([=] {
  1115. _subscribe->startGlareAnimation();
  1116. }, _subscribe->lifetime());
  1117. content->widthValue(
  1118. ) | rpl::start_with_next([=](int width) {
  1119. const auto padding = st::settingsPremiumButtonPadding;
  1120. _subscribe->resizeToWidth(width - padding.left() - padding.right());
  1121. }, _subscribe->lifetime());
  1122. rpl::combine(
  1123. _subscribe->heightValue(),
  1124. Data::AmPremiumValue(session),
  1125. session->premiumPossibleValue()
  1126. ) | rpl::start_with_next([=](
  1127. int buttonHeight,
  1128. bool premium,
  1129. bool premiumPossible) {
  1130. const auto padding = st::settingsPremiumButtonPadding;
  1131. const auto finalHeight = !premiumPossible
  1132. ? 0
  1133. : !premium
  1134. ? (padding.top() + buttonHeight + padding.bottom())
  1135. : 0;
  1136. content->resize(content->width(), finalHeight);
  1137. _subscribe->moveToLeft(padding.left(), padding.top());
  1138. _subscribe->setVisible(!premium && premiumPossible);
  1139. }, _subscribe->lifetime());
  1140. return Ui::MakeWeak(not_null<Ui::RpWidget*>{ content });
  1141. }
  1142. } // namespace
  1143. template <>
  1144. struct SectionFactory<Premium> : AbstractSectionFactory {
  1145. object_ptr<AbstractSection> create(
  1146. not_null<QWidget*> parent,
  1147. not_null<Window::SessionController*> controller,
  1148. not_null<Ui::ScrollArea*> scroll,
  1149. rpl::producer<Container> containerValue
  1150. ) const final override {
  1151. return object_ptr<Premium>(parent, controller);
  1152. }
  1153. bool hasCustomTopBar() const final override {
  1154. return true;
  1155. }
  1156. [[nodiscard]] static const std::shared_ptr<SectionFactory> &Instance() {
  1157. static const auto result = std::make_shared<SectionFactory>();
  1158. return result;
  1159. }
  1160. };
  1161. Type PremiumId() {
  1162. return Premium::Id();
  1163. }
  1164. void ShowPremium(not_null<Main::Session*> session, const QString &ref) {
  1165. const auto active = Core::App().activeWindow();
  1166. const auto controller = (active && active->isPrimary())
  1167. ? active->sessionController()
  1168. : nullptr;
  1169. if (controller && session == &controller->session()) {
  1170. ShowPremium(controller, ref);
  1171. } else {
  1172. for (const auto &controller : session->windows()) {
  1173. if (controller->window().isPrimary()) {
  1174. ShowPremium(controller, ref);
  1175. }
  1176. }
  1177. }
  1178. }
  1179. void ShowPremium(
  1180. not_null<Window::SessionController*> controller,
  1181. const QString &ref) {
  1182. if (!controller->session().premiumPossible()) {
  1183. controller->show(Box(PremiumUnavailableBox));
  1184. return;
  1185. }
  1186. controller->setPremiumRef(ref);
  1187. controller->showSettings(Settings::PremiumId());
  1188. }
  1189. void ShowGiftPremium(
  1190. not_null<Window::SessionController*> controller,
  1191. not_null<PeerData*> peer,
  1192. int months,
  1193. bool me) {
  1194. ShowPremium(controller, Ref::Gift::Serialize({ peer->id, months, me }));
  1195. }
  1196. void ShowEmojiStatusPremium(
  1197. not_null<Window::SessionController*> controller,
  1198. not_null<PeerData*> peer) {
  1199. if (const auto unique = peer->emojiStatusId().collectible.get()) {
  1200. Core::ResolveAndShowUniqueGift(controller->uiShow(), unique->slug);
  1201. } else {
  1202. ShowPremium(controller, Ref::EmojiStatus::Serialize({ peer->id }));
  1203. }
  1204. }
  1205. void StartPremiumPayment(
  1206. not_null<Window::SessionController*> controller,
  1207. const QString &ref) {
  1208. const auto session = &controller->session();
  1209. const auto username = session->appConfig().get<QString>(
  1210. u"premium_bot_username"_q,
  1211. QString());
  1212. const auto slug = session->appConfig().get<QString>(
  1213. u"premium_invoice_slug"_q,
  1214. QString());
  1215. if (!username.isEmpty()) {
  1216. controller->showPeerByLink(Window::PeerByLinkInfo{
  1217. .usernameOrId = username,
  1218. .resolveType = Window::ResolveType::BotStart,
  1219. .startToken = ref,
  1220. .startAutoSubmit = true,
  1221. });
  1222. } else if (!slug.isEmpty()) {
  1223. UrlClickHandler::Open("https://t.me/$" + slug);
  1224. }
  1225. }
  1226. QString LookupPremiumRef(PremiumFeature section) {
  1227. for (const auto &[ref, entry] : EntryMap()) {
  1228. if (entry.section == section) {
  1229. return ref;
  1230. }
  1231. }
  1232. return QString();
  1233. }
  1234. void ShowPremiumPromoToast(
  1235. std::shared_ptr<ChatHelpers::Show> show,
  1236. TextWithEntities textWithLink,
  1237. const QString &ref) {
  1238. ShowPremiumPromoToast(show, [=](
  1239. not_null<Main::Session*> session) {
  1240. Expects(&show->session() == session);
  1241. return show->resolveWindow();
  1242. }, std::move(textWithLink), ref);
  1243. }
  1244. void ShowPremiumPromoToast(
  1245. std::shared_ptr<Main::SessionShow> show,
  1246. Fn<Window::SessionController*(
  1247. not_null<Main::Session*>)> resolveWindow,
  1248. TextWithEntities textWithLink,
  1249. const QString &ref) {
  1250. using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
  1251. const auto toast = std::make_shared<WeakToast>();
  1252. (*toast) = show->showToast({
  1253. .text = std::move(textWithLink),
  1254. .filter = crl::guard(&show->session(), [=](
  1255. const ClickHandlerPtr &,
  1256. Qt::MouseButton button) {
  1257. if (button == Qt::LeftButton) {
  1258. if (const auto strong = toast->get()) {
  1259. strong->hideAnimated();
  1260. (*toast) = nullptr;
  1261. if (const auto controller = resolveWindow(
  1262. &show->session())) {
  1263. Settings::ShowPremium(controller, ref);
  1264. }
  1265. return true;
  1266. }
  1267. }
  1268. return false;
  1269. }),
  1270. .adaptive = true,
  1271. .duration = Ui::Toast::kDefaultDuration * 2,
  1272. });
  1273. }
  1274. not_null<Ui::RoundButton*> CreateLockedButton(
  1275. not_null<QWidget*> parent,
  1276. rpl::producer<QString> text,
  1277. const style::RoundButton &st,
  1278. rpl::producer<bool> locked) {
  1279. const auto result = Ui::CreateChild<Ui::RoundButton>(
  1280. parent.get(),
  1281. rpl::single(QString()),
  1282. st);
  1283. const auto labelSt = result->lifetime().make_state<style::FlatLabel>(
  1284. st::defaultFlatLabel);
  1285. labelSt->style.font = st.style.font;
  1286. labelSt->textFg = st.textFg;
  1287. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  1288. result,
  1289. std::move(text),
  1290. *labelSt);
  1291. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  1292. const auto icon = Ui::CreateChild<Ui::RpWidget>(result);
  1293. icon->setAttribute(Qt::WA_TransparentForMouseEvents);
  1294. icon->resize(st::stickersPremiumLock.size());
  1295. icon->paintRequest() | rpl::start_with_next([=] {
  1296. auto p = QPainter(icon);
  1297. st::stickersPremiumLock.paint(p, 0, 0, icon->width());
  1298. }, icon->lifetime());
  1299. rpl::combine(
  1300. result->widthValue(),
  1301. label->widthValue(),
  1302. std::move(locked)
  1303. ) | rpl::start_with_next([=](int outer, int inner, bool locked) {
  1304. if (locked) {
  1305. icon->show();
  1306. inner += icon->width();
  1307. label->move(
  1308. (outer - inner) / 2 + icon->width(),
  1309. st::similarChannelsLock.textTop);
  1310. icon->move(
  1311. (outer - inner) / 2,
  1312. st::similarChannelsLock.textTop);
  1313. } else {
  1314. icon->hide();
  1315. label->move(
  1316. (outer - inner) / 2,
  1317. st::similarChannelsLock.textTop);
  1318. }
  1319. }, result->lifetime());
  1320. return result;
  1321. }
  1322. not_null<Ui::GradientButton*> CreateSubscribeButton(
  1323. SubscribeButtonArgs &&args) {
  1324. Expects(args.show || args.controller);
  1325. auto show = args.show ? std::move(args.show) : args.controller->uiShow();
  1326. auto resolve = [show](not_null<Main::Session*> session) {
  1327. Expects(session == &show->session());
  1328. return show->resolveWindow();
  1329. };
  1330. return CreateSubscribeButton(
  1331. std::move(show),
  1332. std::move(resolve),
  1333. std::move(args));
  1334. }
  1335. not_null<Ui::GradientButton*> CreateSubscribeButton(
  1336. std::shared_ptr<::Main::SessionShow> show,
  1337. Fn<Window::SessionController*(
  1338. not_null<::Main::Session*>)> resolveWindow,
  1339. SubscribeButtonArgs &&args) {
  1340. const auto result = Ui::CreateChild<Ui::GradientButton>(
  1341. args.parent.get(),
  1342. args.gradientStops
  1343. ? base::take(*args.gradientStops)
  1344. : Ui::Premium::ButtonGradientStops());
  1345. result->setClickedCallback([
  1346. show,
  1347. resolveWindow,
  1348. promo = args.showPromo,
  1349. computeRef = args.computeRef,
  1350. computeBotUrl = args.computeBotUrl] {
  1351. const auto window = resolveWindow(
  1352. &show->session());
  1353. if (!window) {
  1354. return;
  1355. } else if (promo) {
  1356. Settings::ShowPremium(window, computeRef());
  1357. return;
  1358. }
  1359. const auto url = computeBotUrl ? computeBotUrl() : QString();
  1360. if (!url.isEmpty()) {
  1361. const auto local = Core::TryConvertUrlToLocal(url);
  1362. if (local.isEmpty()) {
  1363. return;
  1364. }
  1365. UrlClickHandler::Open(
  1366. local,
  1367. QVariant::fromValue(ClickHandlerContext{
  1368. .sessionWindow = base::make_weak(window),
  1369. .botStartAutoSubmit = true,
  1370. }));
  1371. } else {
  1372. SendScreenAccept(window);
  1373. StartPremiumPayment(window, computeRef());
  1374. }
  1375. });
  1376. const auto &st = st::premiumPreviewBox.button;
  1377. result->resize(args.parent->width(), st.height);
  1378. const auto premium = &show->session().api().premium();
  1379. premium->reload();
  1380. const auto computeCost = [=] {
  1381. const auto amount = premium->monthlyAmount();
  1382. const auto currency = premium->monthlyCurrency();
  1383. const auto valid = (amount > 0) && !currency.isEmpty();
  1384. return Ui::FillAmountAndCurrency(
  1385. valid ? amount : 500,
  1386. valid ? currency : "USD");
  1387. };
  1388. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  1389. result,
  1390. args.text
  1391. ? base::take(*args.text)
  1392. : tr::lng_premium_summary_button(
  1393. lt_cost,
  1394. premium->statusTextValue() | rpl::map(computeCost)),
  1395. st::premiumPreviewButtonLabel);
  1396. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  1397. rpl::combine(
  1398. result->widthValue(),
  1399. label->widthValue()
  1400. ) | rpl::start_with_next([=](int outer, int width) {
  1401. label->moveToLeft(
  1402. (outer - width) / 2,
  1403. st::premiumPreviewBox.button.textTop,
  1404. outer);
  1405. }, label->lifetime());
  1406. return result;
  1407. }
  1408. std::vector<PremiumFeature> PremiumFeaturesOrder(
  1409. not_null<Main::Session*> session) {
  1410. const auto mtpOrder = session->appConfig().get<Order>(
  1411. "premium_promo_order",
  1412. FallbackOrder());
  1413. return ranges::views::all(
  1414. mtpOrder
  1415. ) | ranges::views::transform([](const QString &s) {
  1416. if (s == u"more_upload"_q) {
  1417. return PremiumFeature::MoreUpload;
  1418. } else if (s == u"faster_download"_q) {
  1419. return PremiumFeature::FasterDownload;
  1420. } else if (s == u"voice_to_text"_q) {
  1421. return PremiumFeature::VoiceToText;
  1422. } else if (s == u"no_ads"_q) {
  1423. return PremiumFeature::NoAds;
  1424. } else if (s == u"emoji_status"_q) {
  1425. return PremiumFeature::EmojiStatus;
  1426. } else if (s == u"infinite_reactions"_q) {
  1427. return PremiumFeature::InfiniteReactions;
  1428. } else if (s == u"saved_tags"_q) {
  1429. return PremiumFeature::TagsForMessages;
  1430. } else if (s == u"last_seen"_q) {
  1431. return PremiumFeature::LastSeen;
  1432. } else if (s == u"message_privacy"_q) {
  1433. return PremiumFeature::MessagePrivacy;
  1434. } else if (s == u"premium_stickers"_q) {
  1435. return PremiumFeature::Stickers;
  1436. } else if (s == u"animated_emoji"_q) {
  1437. return PremiumFeature::AnimatedEmoji;
  1438. } else if (s == u"advanced_chat_management"_q) {
  1439. return PremiumFeature::AdvancedChatManagement;
  1440. } else if (s == u"profile_badge"_q) {
  1441. return PremiumFeature::ProfileBadge;
  1442. } else if (s == u"animated_userpics"_q) {
  1443. return PremiumFeature::AnimatedUserpics;
  1444. } else if (s == u"translations"_q) {
  1445. return PremiumFeature::RealTimeTranslation;
  1446. } else if (s == u"wallpapers"_q) {
  1447. return PremiumFeature::Wallpapers;
  1448. } else if (s == u"effects"_q) {
  1449. return PremiumFeature::Effects;
  1450. }
  1451. return PremiumFeature::kCount;
  1452. }) | ranges::views::filter([](PremiumFeature type) {
  1453. return (type != PremiumFeature::kCount);
  1454. }) | ranges::to_vector;
  1455. }
  1456. void AddSummaryPremium(
  1457. not_null<Ui::VerticalLayout*> content,
  1458. not_null<Window::SessionController*> controller,
  1459. const QString &ref,
  1460. Fn<void(PremiumFeature)> buttonCallback) {
  1461. const auto &stDefault = st::settingsButton;
  1462. const auto &stLabel = st::defaultFlatLabel;
  1463. const auto iconSize = st::settingsPremiumIconDouble.size();
  1464. const auto &titlePadding = st::settingsPremiumRowTitlePadding;
  1465. const auto &descriptionPadding = st::settingsPremiumRowAboutPadding;
  1466. auto entryMap = EntryMap();
  1467. auto iconContainers = std::vector<Ui::AbstractButton*>();
  1468. iconContainers.reserve(int(entryMap.size()));
  1469. const auto addRow = [&](Entry &entry) {
  1470. const auto labelAscent = stLabel.style.font->ascent;
  1471. const auto button = Ui::CreateChild<Ui::SettingsButton>(
  1472. content.get(),
  1473. rpl::single(QString()));
  1474. const auto label = content->add(
  1475. object_ptr<Ui::FlatLabel>(
  1476. content,
  1477. std::move(entry.title) | Ui::Text::ToBold(),
  1478. stLabel),
  1479. titlePadding);
  1480. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  1481. const auto description = content->add(
  1482. object_ptr<Ui::FlatLabel>(
  1483. content,
  1484. std::move(entry.description),
  1485. st::boxDividerLabel),
  1486. descriptionPadding);
  1487. description->setAttribute(Qt::WA_TransparentForMouseEvents);
  1488. if (entry.newBadge) {
  1489. Ui::NewBadge::AddAfterLabel(content, label);
  1490. }
  1491. const auto dummy = Ui::CreateChild<Ui::AbstractButton>(content.get());
  1492. dummy->setAttribute(Qt::WA_TransparentForMouseEvents);
  1493. content->sizeValue(
  1494. ) | rpl::start_with_next([=](const QSize &s) {
  1495. dummy->resize(s.width(), iconSize.height());
  1496. }, dummy->lifetime());
  1497. label->geometryValue(
  1498. ) | rpl::start_with_next([=](const QRect &r) {
  1499. dummy->moveToLeft(0, r.y() + (r.height() - labelAscent));
  1500. }, dummy->lifetime());
  1501. rpl::combine(
  1502. content->widthValue(),
  1503. label->heightValue(),
  1504. description->heightValue()
  1505. ) | rpl::start_with_next([=,
  1506. topPadding = titlePadding,
  1507. bottomPadding = descriptionPadding](
  1508. int width,
  1509. int topHeight,
  1510. int bottomHeight) {
  1511. button->resize(
  1512. width,
  1513. topPadding.top()
  1514. + topHeight
  1515. + topPadding.bottom()
  1516. + bottomPadding.top()
  1517. + bottomHeight
  1518. + bottomPadding.bottom());
  1519. }, button->lifetime());
  1520. label->topValue(
  1521. ) | rpl::start_with_next([=, padding = titlePadding.top()](int top) {
  1522. button->moveToLeft(0, top - padding);
  1523. }, button->lifetime());
  1524. const auto arrow = Ui::CreateChild<Ui::IconButton>(
  1525. button,
  1526. st::backButton);
  1527. arrow->setIconOverride(
  1528. &st::settingsPremiumArrow,
  1529. &st::settingsPremiumArrowOver);
  1530. arrow->setAttribute(Qt::WA_TransparentForMouseEvents);
  1531. button->sizeValue(
  1532. ) | rpl::start_with_next([=](const QSize &s) {
  1533. const auto &point = st::settingsPremiumArrowShift;
  1534. arrow->moveToRight(
  1535. -point.x(),
  1536. point.y() + (s.height() - arrow->height()) / 2);
  1537. }, arrow->lifetime());
  1538. const auto section = entry.section;
  1539. button->setClickedCallback([=] { buttonCallback(section); });
  1540. iconContainers.push_back(dummy);
  1541. };
  1542. auto icons = std::vector<const style::icon *>();
  1543. icons.reserve(int(entryMap.size()));
  1544. {
  1545. const auto session = &controller->session();
  1546. const auto mtpOrder = session->appConfig().get<Order>(
  1547. "premium_promo_order",
  1548. FallbackOrder());
  1549. const auto processEntry = [&](Entry &entry) {
  1550. icons.push_back(entry.icon);
  1551. addRow(entry);
  1552. };
  1553. for (const auto &key : mtpOrder) {
  1554. auto it = entryMap.find(key);
  1555. if (it == end(entryMap)) {
  1556. continue;
  1557. }
  1558. processEntry(it->second);
  1559. }
  1560. SendScreenShow(controller, mtpOrder, ref);
  1561. }
  1562. content->resizeToWidth(content->height());
  1563. // Icons.
  1564. Assert(iconContainers.size() > 2);
  1565. const auto from = iconContainers.front()->y();
  1566. const auto to = iconContainers.back()->y() + iconSize.height();
  1567. auto gradient = QLinearGradient(0, 0, 0, to - from);
  1568. gradient.setStops(Ui::Premium::FullHeightGradientStops());
  1569. for (auto i = 0; i < int(icons.size()); i++) {
  1570. const auto &iconContainer = iconContainers[i];
  1571. const auto pointTop = iconContainer->y() - from;
  1572. const auto pointBottom = pointTop + iconContainer->height();
  1573. const auto ratioTop = pointTop / float64(to - from);
  1574. const auto ratioBottom = pointBottom / float64(to - from);
  1575. auto resultGradient = QLinearGradient(
  1576. QPointF(),
  1577. QPointF(0, pointBottom - pointTop));
  1578. resultGradient.setColorAt(
  1579. .0,
  1580. anim::gradient_color_at(gradient, ratioTop));
  1581. resultGradient.setColorAt(
  1582. .1,
  1583. anim::gradient_color_at(gradient, ratioBottom));
  1584. const auto brush = QBrush(resultGradient);
  1585. AddButtonIcon(
  1586. iconContainer,
  1587. stDefault,
  1588. { .icon = icons[i], .backgroundBrush = brush });
  1589. }
  1590. Ui::AddSkip(content, descriptionPadding.bottom());
  1591. }
  1592. std::unique_ptr<Ui::RpWidget> MakeEmojiStatusPreview(
  1593. not_null<QWidget*> parent,
  1594. not_null<DocumentData*> document) {
  1595. auto result = std::make_unique<Ui::RpWidget>(parent);
  1596. const auto raw = result.get();
  1597. const auto size = HistoryView::Sticker::EmojiSize();
  1598. const auto emoji = raw->lifetime().make_state<EmojiStatusTopBar>(
  1599. document,
  1600. [=](QRect r) { raw->update(std::move(r)); },
  1601. size);
  1602. raw->paintRequest() | rpl::start_with_next([=] {
  1603. auto p = QPainter(raw);
  1604. emoji->paint(p);
  1605. }, raw->lifetime());
  1606. raw->sizeValue() | rpl::start_with_next([=](QSize size) {
  1607. emoji->setCenter(QPointF(size.width() / 2., size.height() / 2.));
  1608. }, raw->lifetime());
  1609. return result;
  1610. }
  1611. } // namespace Settings