premium_preview_box.cpp 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698
  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 "boxes/premium_preview_box.h"
  8. #include "chat_helpers/stickers_lottie.h"
  9. #include "chat_helpers/stickers_emoji_pack.h"
  10. #include "data/data_file_origin.h"
  11. #include "data/data_document.h"
  12. #include "data/data_session.h"
  13. #include "data/data_document_media.h"
  14. #include "data/data_streaming.h"
  15. #include "data/data_peer_values.h"
  16. #include "data/data_premium_limits.h"
  17. #include "lang/lang_keys.h"
  18. #include "main/main_session.h"
  19. #include "main/main_domain.h" // kMaxAccounts
  20. #include "ui/chat/chat_theme.h"
  21. #include "ui/chat/chat_style.h"
  22. #include "ui/layers/generic_box.h"
  23. #include "ui/effects/path_shift_gradient.h"
  24. #include "ui/effects/premium_graphics.h"
  25. #include "ui/effects/gradient.h"
  26. #include "ui/text/text.h"
  27. #include "ui/text/text_utilities.h"
  28. #include "ui/widgets/buttons.h"
  29. #include "ui/widgets/gradient_round_button.h"
  30. #include "ui/wrap/padding_wrap.h"
  31. #include "ui/boxes/confirm_box.h"
  32. #include "ui/painter.h"
  33. #include "ui/vertical_list.h"
  34. #include "settings/settings_business.h"
  35. #include "settings/settings_premium.h"
  36. #include "lottie/lottie_single_player.h"
  37. #include "history/view/media/history_view_sticker.h"
  38. #include "history/view/history_view_element.h"
  39. #include "media/streaming/media_streaming_instance.h"
  40. #include "media/streaming/media_streaming_player.h"
  41. #include "window/window_session_controller.h"
  42. #include "api/api_premium.h"
  43. #include "apiwrap.h"
  44. #include "styles/style_layers.h"
  45. #include "styles/style_premium.h"
  46. #include "styles/style_settings.h"
  47. #include <QSvgRenderer>
  48. namespace {
  49. constexpr auto kPremiumShift = 21. / 240;
  50. constexpr auto kToggleStickerTimeout = 2 * crl::time(1000);
  51. constexpr auto kStarOpacityOff = 0.1;
  52. constexpr auto kStarOpacityOn = 1.;
  53. constexpr auto kStarPeriod = 3 * crl::time(1000);
  54. using Data::ReactionId;
  55. struct Descriptor {
  56. PremiumFeature section = PremiumFeature::Stickers;
  57. DocumentData *requestedSticker = nullptr;
  58. bool fromSettings = false;
  59. Fn<void()> hiddenCallback;
  60. Fn<void(not_null<Ui::BoxContent*>)> shownCallback;
  61. bool hideSubscriptionButton = false;
  62. };
  63. bool operator==(const Descriptor &a, const Descriptor &b) {
  64. return (a.section == b.section)
  65. && (a.requestedSticker == b.requestedSticker)
  66. && (a.fromSettings == b.fromSettings);
  67. }
  68. struct Preload {
  69. Descriptor descriptor;
  70. std::shared_ptr<Data::DocumentMedia> media;
  71. std::weak_ptr<Main::SessionShow> show;
  72. };
  73. [[nodiscard]] std::vector<Preload> &Preloads() {
  74. static auto result = std::vector<Preload>();
  75. return result;
  76. }
  77. void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
  78. const auto origin = media->owner()->stickerSetOrigin();
  79. media->automaticLoad(origin, nullptr);
  80. media->videoThumbnailWanted(origin);
  81. }
  82. [[nodiscard]] rpl::producer<QString> SectionTitle(PremiumFeature section) {
  83. switch (section) {
  84. case PremiumFeature::Wallpapers:
  85. return tr::lng_premium_summary_subtitle_wallpapers();
  86. case PremiumFeature::Stories:
  87. return tr::lng_premium_summary_subtitle_stories();
  88. case PremiumFeature::DoubleLimits:
  89. return tr::lng_premium_summary_subtitle_double_limits();
  90. case PremiumFeature::MoreUpload:
  91. return tr::lng_premium_summary_subtitle_more_upload();
  92. case PremiumFeature::FasterDownload:
  93. return tr::lng_premium_summary_subtitle_faster_download();
  94. case PremiumFeature::VoiceToText:
  95. return tr::lng_premium_summary_subtitle_voice_to_text();
  96. case PremiumFeature::NoAds:
  97. return tr::lng_premium_summary_subtitle_no_ads();
  98. case PremiumFeature::EmojiStatus:
  99. return tr::lng_premium_summary_subtitle_emoji_status();
  100. case PremiumFeature::InfiniteReactions:
  101. return tr::lng_premium_summary_subtitle_infinite_reactions();
  102. case PremiumFeature::TagsForMessages:
  103. return tr::lng_premium_summary_subtitle_tags_for_messages();
  104. case PremiumFeature::LastSeen:
  105. return tr::lng_premium_summary_subtitle_last_seen();
  106. case PremiumFeature::MessagePrivacy:
  107. return tr::lng_premium_summary_subtitle_message_privacy();
  108. case PremiumFeature::Stickers:
  109. return tr::lng_premium_summary_subtitle_premium_stickers();
  110. case PremiumFeature::AnimatedEmoji:
  111. return tr::lng_premium_summary_subtitle_animated_emoji();
  112. case PremiumFeature::AdvancedChatManagement:
  113. return tr::lng_premium_summary_subtitle_advanced_chat_management();
  114. case PremiumFeature::ProfileBadge:
  115. return tr::lng_premium_summary_subtitle_profile_badge();
  116. case PremiumFeature::AnimatedUserpics:
  117. return tr::lng_premium_summary_subtitle_animated_userpics();
  118. case PremiumFeature::RealTimeTranslation:
  119. return tr::lng_premium_summary_subtitle_translation();
  120. case PremiumFeature::Business:
  121. return tr::lng_premium_summary_subtitle_business();
  122. case PremiumFeature::Effects:
  123. return tr::lng_premium_summary_subtitle_effects();
  124. case PremiumFeature::BusinessLocation:
  125. return tr::lng_business_subtitle_location();
  126. case PremiumFeature::BusinessHours:
  127. return tr::lng_business_subtitle_opening_hours();
  128. case PremiumFeature::QuickReplies:
  129. return tr::lng_business_subtitle_quick_replies();
  130. case PremiumFeature::GreetingMessage:
  131. return tr::lng_business_subtitle_greeting_messages();
  132. case PremiumFeature::AwayMessage:
  133. return tr::lng_business_subtitle_away_messages();
  134. case PremiumFeature::BusinessBots:
  135. return tr::lng_business_subtitle_chatbots();
  136. case PremiumFeature::ChatIntro:
  137. return tr::lng_business_subtitle_chat_intro();
  138. case PremiumFeature::ChatLinks:
  139. return tr::lng_business_subtitle_chat_links();
  140. case PremiumFeature::FilterTags:
  141. return tr::lng_premium_summary_subtitle_filter_tags();
  142. }
  143. Unexpected("PremiumFeature in SectionTitle.");
  144. }
  145. [[nodiscard]] rpl::producer<QString> SectionAbout(PremiumFeature section) {
  146. switch (section) {
  147. case PremiumFeature::Wallpapers:
  148. return tr::lng_premium_summary_about_wallpapers();
  149. case PremiumFeature::Stories:
  150. return tr::lng_premium_summary_about_stories();
  151. case PremiumFeature::DoubleLimits:
  152. return tr::lng_premium_summary_about_double_limits();
  153. case PremiumFeature::MoreUpload:
  154. return tr::lng_premium_summary_about_more_upload();
  155. case PremiumFeature::FasterDownload:
  156. return tr::lng_premium_summary_about_faster_download();
  157. case PremiumFeature::VoiceToText:
  158. return tr::lng_premium_summary_about_voice_to_text();
  159. case PremiumFeature::NoAds:
  160. return tr::lng_premium_summary_about_no_ads();
  161. case PremiumFeature::EmojiStatus:
  162. return tr::lng_premium_summary_about_emoji_status();
  163. case PremiumFeature::InfiniteReactions:
  164. return tr::lng_premium_summary_about_infinite_reactions();
  165. case PremiumFeature::TagsForMessages:
  166. return tr::lng_premium_summary_about_tags_for_messages();
  167. case PremiumFeature::LastSeen:
  168. return tr::lng_premium_summary_about_last_seen();
  169. case PremiumFeature::MessagePrivacy:
  170. return tr::lng_premium_summary_about_message_privacy();
  171. case PremiumFeature::Stickers:
  172. return tr::lng_premium_summary_about_premium_stickers();
  173. case PremiumFeature::AnimatedEmoji:
  174. return tr::lng_premium_summary_about_animated_emoji();
  175. case PremiumFeature::AdvancedChatManagement:
  176. return tr::lng_premium_summary_about_advanced_chat_management();
  177. case PremiumFeature::ProfileBadge:
  178. return tr::lng_premium_summary_about_profile_badge();
  179. case PremiumFeature::AnimatedUserpics:
  180. return tr::lng_premium_summary_about_animated_userpics();
  181. case PremiumFeature::RealTimeTranslation:
  182. return tr::lng_premium_summary_about_translation();
  183. case PremiumFeature::Business:
  184. return tr::lng_premium_summary_about_business();
  185. case PremiumFeature::Effects:
  186. return tr::lng_premium_summary_about_effects();
  187. case PremiumFeature::BusinessLocation:
  188. return tr::lng_business_about_location();
  189. case PremiumFeature::BusinessHours:
  190. return tr::lng_business_about_opening_hours();
  191. case PremiumFeature::QuickReplies:
  192. return tr::lng_business_about_quick_replies();
  193. case PremiumFeature::GreetingMessage:
  194. return tr::lng_business_about_greeting_messages();
  195. case PremiumFeature::AwayMessage:
  196. return tr::lng_business_about_away_messages();
  197. case PremiumFeature::BusinessBots:
  198. return tr::lng_business_about_chatbots();
  199. case PremiumFeature::ChatIntro:
  200. return tr::lng_business_about_chat_intro();
  201. case PremiumFeature::ChatLinks:
  202. return tr::lng_business_about_chat_links();
  203. case PremiumFeature::FilterTags:
  204. return tr::lng_premium_summary_about_filter_tags();
  205. }
  206. Unexpected("PremiumFeature in SectionTitle.");
  207. }
  208. [[nodiscard]] object_ptr<Ui::RpWidget> ChatBackPreview(
  209. QWidget *parent,
  210. int height,
  211. const QImage &back) {
  212. auto result = object_ptr<Ui::FixedHeightWidget>(parent, height);
  213. const auto raw = result.data();
  214. raw->paintRequest(
  215. ) | rpl::start_with_next([=] {
  216. auto p = QPainter(raw);
  217. p.drawImage(0, 0, back);
  218. }, raw->lifetime());
  219. return result;
  220. }
  221. [[nodiscard]] not_null<Ui::RpWidget*> StickerPreview(
  222. not_null<Ui::RpWidget*> parent,
  223. std::shared_ptr<ChatHelpers::Show> show,
  224. const std::shared_ptr<Data::DocumentMedia> &media,
  225. Fn<void()> readyCallback = nullptr) {
  226. using namespace HistoryView;
  227. PreloadSticker(media);
  228. const auto document = media->owner();
  229. const auto lottieSize = Sticker::Size(document);
  230. const auto effectSize = Sticker::PremiumEffectSize(document);
  231. const auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
  232. result->show();
  233. parent->sizeValue(
  234. ) | rpl::start_with_next([=](QSize size) {
  235. result->setGeometry(QRect(
  236. QPoint(
  237. (size.width() - effectSize.width()) / 2,
  238. (size.height() - effectSize.height()) / 2),
  239. effectSize));
  240. }, result->lifetime());
  241. auto &lifetime = result->lifetime();
  242. struct State {
  243. std::unique_ptr<Lottie::SinglePlayer> lottie;
  244. std::unique_ptr<Lottie::SinglePlayer> effect;
  245. style::owned_color pathFg = style::owned_color(
  246. QColor(255, 255, 255, 64));
  247. std::unique_ptr<Ui::PathShiftGradient> pathGradient;
  248. bool readyInvoked = false;
  249. };
  250. const auto state = lifetime.make_state<State>();
  251. const auto createLottieIfReady = [=] {
  252. if (state->lottie) {
  253. return;
  254. }
  255. const auto document = media->owner();
  256. const auto sticker = document->sticker();
  257. if (!sticker || !sticker->isLottie() || !media->loaded()) {
  258. return;
  259. } else if (media->videoThumbnailContent().isEmpty()) {
  260. return;
  261. }
  262. const auto factor = style::DevicePixelRatio();
  263. state->lottie = ChatHelpers::LottiePlayerFromDocument(
  264. media.get(),
  265. nullptr,
  266. ChatHelpers::StickerLottieSize::MessageHistory,
  267. lottieSize * factor,
  268. Lottie::Quality::High);
  269. state->effect = document->session().emojiStickersPack().effectPlayer(
  270. document,
  271. media->videoThumbnailContent(),
  272. QString(),
  273. Stickers::EffectType::PremiumSticker);
  274. const auto update = [=] {
  275. if (!state->readyInvoked
  276. && readyCallback
  277. && state->lottie->ready()
  278. && state->effect->ready()) {
  279. state->readyInvoked = true;
  280. readyCallback();
  281. }
  282. result->update();
  283. };
  284. auto &lifetime = result->lifetime();
  285. state->lottie->updates() | rpl::start_with_next(update, lifetime);
  286. state->effect->updates() | rpl::start_with_next(update, lifetime);
  287. };
  288. createLottieIfReady();
  289. if (!state->lottie || !state->effect) {
  290. show->session().downloaderTaskFinished(
  291. ) | rpl::take_while([=] {
  292. createLottieIfReady();
  293. return !state->lottie || !state->effect;
  294. }) | rpl::start(result->lifetime());
  295. }
  296. state->pathGradient = std::make_unique<Ui::PathShiftGradient>(
  297. st::shadowFg,
  298. state->pathFg.color(),
  299. [=] { result->update(); },
  300. rpl::never<>());
  301. result->paintRequest(
  302. ) | rpl::start_with_next([=] {
  303. createLottieIfReady();
  304. auto p = QPainter(result);
  305. const auto left = effectSize.width()
  306. - int(lottieSize.width() * (1. + kPremiumShift));
  307. const auto top = (effectSize.height() - lottieSize.height()) / 2;
  308. const auto r = QRect(QPoint(left, top), lottieSize);
  309. if (!state->lottie
  310. || !state->lottie->ready()
  311. || !state->effect->ready()) {
  312. p.setBrush(st::shadowFg);
  313. ChatHelpers::PaintStickerThumbnailPath(
  314. p,
  315. media.get(),
  316. r,
  317. state->pathGradient.get());
  318. return;
  319. }
  320. const auto factor = style::DevicePixelRatio();
  321. const auto frame = state->lottie->frameInfo({ lottieSize * factor });
  322. const auto effect = state->effect->frameInfo(
  323. { effectSize * factor });
  324. //const auto framesCount = !frame.image.isNull()
  325. // ? state->lottie->framesCount()
  326. // : 1;
  327. //const auto effectsCount = !effect.image.isNull()
  328. // ? state->effect->framesCount()
  329. // : 1;
  330. p.drawImage(r, frame.image);
  331. p.drawImage(
  332. QRect(QPoint(), effect.image.size() / factor),
  333. effect.image);
  334. if (!frame.image.isNull()/*
  335. && ((frame.index % effectsCount) <= effect.index)*/) {
  336. state->lottie->markFrameShown();
  337. }
  338. if (!effect.image.isNull()/*
  339. && ((effect.index % framesCount) <= frame.index)*/) {
  340. state->effect->markFrameShown();
  341. }
  342. }, lifetime);
  343. return result;
  344. }
  345. [[nodiscard]] not_null<Ui::RpWidget*> StickersPreview(
  346. not_null<Ui::RpWidget*> parent,
  347. std::shared_ptr<ChatHelpers::Show> show,
  348. Fn<void()> readyCallback) {
  349. const auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
  350. result->show();
  351. parent->sizeValue(
  352. ) | rpl::start_with_next([=](QSize size) {
  353. result->setGeometry(QRect(QPoint(), size));
  354. }, result->lifetime());
  355. auto &lifetime = result->lifetime();
  356. struct State {
  357. std::vector<std::shared_ptr<Data::DocumentMedia>> medias;
  358. Ui::RpWidget *previous = nullptr;
  359. Ui::RpWidget *current = nullptr;
  360. Ui::RpWidget *next = nullptr;
  361. Ui::Animations::Simple slide;
  362. base::Timer toggleTimer;
  363. bool toggleTimerPending = false;
  364. Fn<void()> singleReadyCallback;
  365. bool readyInvoked = false;
  366. bool timerFired = false;
  367. bool nextReady = false;
  368. int index = 0;
  369. };
  370. const auto premium = &show->session().api().premium();
  371. const auto state = lifetime.make_state<State>();
  372. const auto create = [=](std::shared_ptr<Data::DocumentMedia> media) {
  373. const auto outer = Ui::CreateChild<Ui::RpWidget>(result);
  374. outer->show();
  375. result->sizeValue(
  376. ) | rpl::start_with_next([=](QSize size) {
  377. outer->resize(size);
  378. }, outer->lifetime());
  379. [[maybe_unused]] const auto sticker = StickerPreview(
  380. outer,
  381. show,
  382. media,
  383. state->singleReadyCallback);
  384. return outer;
  385. };
  386. const auto createNext = [=] {
  387. state->nextReady = false;
  388. state->next = create(state->medias[state->index]);
  389. state->next->move(0, state->current->height());
  390. };
  391. const auto check = [=] {
  392. if (!state->timerFired || !state->nextReady) {
  393. return;
  394. }
  395. const auto animationCallback = [=] {
  396. const auto top = int(base::SafeRound(state->slide.value(0.)));
  397. state->previous->move(0, top - state->current->height());
  398. state->current->move(0, top);
  399. if (!state->slide.animating()) {
  400. delete base::take(state->previous);
  401. state->timerFired = false;
  402. state->toggleTimer.callOnce(kToggleStickerTimeout);
  403. }
  404. };
  405. state->timerFired = false;
  406. ++state->index;
  407. state->index %= state->medias.size();
  408. delete std::exchange(state->previous, state->current);
  409. state->current = state->next;
  410. createNext();
  411. state->slide.stop();
  412. state->slide.start(
  413. animationCallback,
  414. state->current->height(),
  415. 0,
  416. st::premiumSlideDuration,
  417. anim::sineInOut);
  418. };
  419. state->toggleTimer.setCallback([=] {
  420. state->timerFired = true;
  421. check();
  422. });
  423. state->singleReadyCallback = [=] {
  424. if (!state->readyInvoked && readyCallback) {
  425. state->readyInvoked = true;
  426. readyCallback();
  427. }
  428. if (!state->next) {
  429. createNext();
  430. if (result->isHidden()) {
  431. state->toggleTimerPending = true;
  432. } else {
  433. state->toggleTimer.callOnce(kToggleStickerTimeout);
  434. }
  435. } else {
  436. state->nextReady = true;
  437. check();
  438. }
  439. };
  440. result->shownValue(
  441. ) | rpl::filter([=](bool shown) {
  442. return shown && state->toggleTimerPending;
  443. }) | rpl::start_with_next([=] {
  444. state->toggleTimerPending = false;
  445. state->toggleTimer.callOnce(kToggleStickerTimeout);
  446. }, result->lifetime());
  447. const auto fill = [=] {
  448. const auto &list = premium->stickers();
  449. for (const auto &document : list) {
  450. state->medias.push_back(document->createMediaView());
  451. }
  452. if (!state->medias.empty()) {
  453. state->current = create(state->medias.front());
  454. state->index = 1 % state->medias.size();
  455. state->current->move(0, 0);
  456. }
  457. };
  458. fill();
  459. if (state->medias.empty()) {
  460. premium->stickersUpdated(
  461. ) | rpl::take(1) | rpl::start_with_next(fill, lifetime);
  462. }
  463. return result;
  464. }
  465. struct VideoPreviewDocument {
  466. DocumentData *document = nullptr;
  467. RectPart align = RectPart::Bottom;
  468. };
  469. [[nodiscard]] bool VideoAlignToTop(PremiumFeature section) {
  470. return (section == PremiumFeature::MoreUpload)
  471. || (section == PremiumFeature::NoAds)
  472. || (section == PremiumFeature::AnimatedEmoji);
  473. }
  474. [[nodiscard]] DocumentData *LookupVideo(
  475. not_null<Main::Session*> session,
  476. PremiumFeature section) {
  477. const auto name = [&] {
  478. switch (section) {
  479. case PremiumFeature::MoreUpload: return "more_upload";
  480. case PremiumFeature::FasterDownload: return "faster_download";
  481. case PremiumFeature::VoiceToText: return "voice_to_text";
  482. case PremiumFeature::NoAds: return "no_ads";
  483. case PremiumFeature::AnimatedEmoji: return "animated_emoji";
  484. case PremiumFeature::AdvancedChatManagement:
  485. return "advanced_chat_management";
  486. case PremiumFeature::EmojiStatus: return "emoji_status";
  487. case PremiumFeature::InfiniteReactions: return "infinite_reactions";
  488. case PremiumFeature::TagsForMessages: return "saved_tags";
  489. case PremiumFeature::ProfileBadge: return "profile_badge";
  490. case PremiumFeature::AnimatedUserpics: return "animated_userpics";
  491. case PremiumFeature::RealTimeTranslation: return "translations";
  492. case PremiumFeature::Wallpapers: return "wallpapers";
  493. case PremiumFeature::LastSeen: return "last_seen";
  494. case PremiumFeature::MessagePrivacy: return "message_privacy";
  495. case PremiumFeature::Effects: return "effects";
  496. case PremiumFeature::BusinessLocation: return "business_location";
  497. case PremiumFeature::BusinessHours: return "business_hours";
  498. case PremiumFeature::QuickReplies: return "quick_replies";
  499. case PremiumFeature::GreetingMessage: return "greeting_message";
  500. case PremiumFeature::AwayMessage: return "away_message";
  501. case PremiumFeature::BusinessBots: return "business_bots";
  502. case PremiumFeature::ChatIntro: return "business_intro";
  503. case PremiumFeature::ChatLinks: return "business_links";
  504. case PremiumFeature::FilterTags: return "folder_tags";
  505. }
  506. return "";
  507. }();
  508. const auto &videos = session->api().premium().videos();
  509. const auto i = videos.find(name);
  510. return (i != end(videos)) ? i->second.get() : nullptr;
  511. }
  512. [[nodiscard]] QPainterPath GenerateFrame(
  513. int left,
  514. int top,
  515. int width,
  516. int height,
  517. bool alignToBottom) {
  518. const auto radius = style::ConvertScaleExact(20.);
  519. const auto thickness = style::ConvertScaleExact(6.);
  520. const auto skip = thickness / 2.;
  521. auto path = QPainterPath();
  522. if (alignToBottom) {
  523. path.moveTo(left - skip, top + height);
  524. path.lineTo(left - skip, top - skip + radius);
  525. path.arcTo(
  526. left - skip,
  527. top - skip,
  528. radius * 2,
  529. radius * 2,
  530. 180,
  531. -90);
  532. path.lineTo(left + width + skip - radius, top - skip);
  533. path.arcTo(
  534. left + width + skip - 2 * radius,
  535. top - skip,
  536. radius * 2,
  537. radius * 2,
  538. 90,
  539. -90);
  540. path.lineTo(left + width + skip, top + height);
  541. } else {
  542. path.moveTo(left - skip, top);
  543. path.lineTo(left - skip, top + height + skip - radius);
  544. path.arcTo(
  545. left - skip,
  546. top + height + skip - 2 * radius,
  547. radius * 2,
  548. radius * 2,
  549. 180,
  550. 90);
  551. path.lineTo(left + width + skip - radius, top + height + skip);
  552. path.arcTo(
  553. left + width + skip - 2 * radius,
  554. top + height + skip - 2 * radius,
  555. radius * 2,
  556. radius * 2,
  557. 270,
  558. 90);
  559. path.lineTo(left + width + skip, top);
  560. }
  561. return path;
  562. }
  563. [[nodiscard]] not_null<Ui::RpWidget*> VideoPreview(
  564. not_null<Ui::RpWidget*> parent,
  565. std::shared_ptr<ChatHelpers::Show> show,
  566. not_null<DocumentData*> document,
  567. bool alignToBottom,
  568. Fn<void()> readyCallback) {
  569. const auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
  570. result->show();
  571. parent->sizeValue(
  572. ) | rpl::start_with_next([=](QSize size) {
  573. result->setGeometry(parent->rect());
  574. }, result->lifetime());
  575. auto &lifetime = result->lifetime();
  576. auto shared = document->owner().streaming().sharedDocument(
  577. document,
  578. Data::FileOriginPremiumPreviews());
  579. if (!shared) {
  580. return result;
  581. }
  582. struct State {
  583. State(
  584. std::shared_ptr<Media::Streaming::Document> shared,
  585. Fn<void()> waitingCallback)
  586. : instance(shared, std::move(waitingCallback))
  587. , star(u":/gui/icons/settings/star.svg"_q) {
  588. }
  589. QImage blurred;
  590. Media::Streaming::Instance instance;
  591. std::shared_ptr<Data::DocumentMedia> media;
  592. Ui::Animations::Basic loading;
  593. QPainterPath frame;
  594. QSvgRenderer star;
  595. bool readyInvoked = false;
  596. };
  597. const auto state = lifetime.make_state<State>(std::move(shared), [] {});
  598. state->media = document->createMediaView();
  599. if (const auto image = state->media->thumbnailInline()) {
  600. if (image->width() > 0) {
  601. const auto width = st::premiumVideoWidth;
  602. const auto height = std::max(
  603. int(base::SafeRound(
  604. float64(width) * image->height() / image->width())),
  605. 1);
  606. using Option = Images::Option;
  607. const auto corners = alignToBottom
  608. ? (Option::RoundSkipBottomLeft
  609. | Option::RoundSkipBottomRight)
  610. : (Option::RoundSkipTopLeft
  611. | Option::RoundSkipTopRight);
  612. state->blurred = Images::Prepare(
  613. image->original(),
  614. QSize(width, height) * style::DevicePixelRatio(),
  615. { .options = (Option::Blur | Option::RoundLarge | corners) });
  616. }
  617. }
  618. const auto width = st::premiumVideoWidth;
  619. const auto height = state->blurred.height()
  620. ? (state->blurred.height() / state->blurred.devicePixelRatio())
  621. : width;
  622. const auto left = (st::boxWideWidth - width) / 2;
  623. const auto top = alignToBottom ? (st::premiumPreviewHeight - height) : 0;
  624. state->frame = GenerateFrame(left, top, width, height, alignToBottom);
  625. const auto check = [=] {
  626. if (state->instance.playerLocked()) {
  627. return;
  628. } else if (state->instance.paused()) {
  629. state->instance.resume();
  630. }
  631. if (!state->instance.active() && !state->instance.failed()) {
  632. auto options = Media::Streaming::PlaybackOptions();
  633. options.waitForMarkAsShown = true;
  634. options.mode = ::Media::Streaming::Mode::Video;
  635. options.loop = true;
  636. state->instance.play(options);
  637. }
  638. };
  639. state->instance.player().updates(
  640. ) | rpl::start_with_next_error([=](Media::Streaming::Update &&update) {
  641. if (v::is<Media::Streaming::Information>(update.data)
  642. || v::is<Media::Streaming::UpdateVideo>(update.data)) {
  643. if (!state->readyInvoked && readyCallback) {
  644. state->readyInvoked = true;
  645. readyCallback();
  646. }
  647. result->update();
  648. }
  649. }, [=](::Media::Streaming::Error &&error) {
  650. result->update();
  651. }, state->instance.lifetime());
  652. state->loading.init([=] {
  653. if (!anim::Disabled()) {
  654. result->update();
  655. }
  656. });
  657. result->paintRequest(
  658. ) | rpl::start_with_next([=] {
  659. auto p = QPainter(result);
  660. const auto paintFrame = [&](QColor color, float64 thickness) {
  661. auto hq = PainterHighQualityEnabler(p);
  662. auto pen = QPen(color);
  663. pen.setWidthF(style::ConvertScaleExact(thickness));
  664. p.setPen(pen);
  665. p.setBrush(Qt::NoBrush);
  666. p.drawPath(state->frame);
  667. };
  668. check();
  669. const auto ready = state->instance.player().ready()
  670. && !state->instance.player().videoSize().isEmpty();
  671. const auto size = QSize(width, height) * style::DevicePixelRatio();
  672. using namespace Images;
  673. auto rounding = CornersMaskRef(
  674. Images::CornersMask(ImageRoundRadius::Large));
  675. if (alignToBottom) {
  676. rounding.p[kBottomLeft] = rounding.p[kBottomRight] = nullptr;
  677. } else {
  678. rounding.p[kTopLeft] = rounding.p[kTopRight] = nullptr;
  679. }
  680. const auto frame = !ready
  681. ? state->blurred
  682. : state->instance.frame({
  683. .resize = size,
  684. .outer = size,
  685. .rounding = rounding,
  686. });
  687. paintFrame(QColor(0, 0, 0, 128), 12.);
  688. p.drawImage(QRect(left, top, width, height), frame);
  689. paintFrame(Qt::black, 6.6);
  690. if (ready) {
  691. state->loading.stop();
  692. state->instance.markFrameShown();
  693. } else {
  694. if (!state->loading.animating()) {
  695. state->loading.start();
  696. }
  697. const auto progress = anim::Disabled()
  698. ? 1.
  699. : ((crl::now() % kStarPeriod) / float64(kStarPeriod));
  700. const auto ratio = anim::Disabled()
  701. ? 1.
  702. : (1. + cos(progress * 2 * M_PI)) / 2.;
  703. const auto opacity = kStarOpacityOff
  704. + (kStarOpacityOn - kStarOpacityOff) * ratio;
  705. p.setOpacity(opacity);
  706. const auto starSize = st::premiumVideoStarSize;
  707. state->star.render(&p, QRectF(
  708. QPointF(
  709. left + (width - starSize.width()) / 2.,
  710. top + (height - starSize.height()) / 2.),
  711. starSize));
  712. }
  713. }, lifetime);
  714. return result;
  715. }
  716. [[nodiscard]] not_null<Ui::RpWidget*> GenericPreview(
  717. not_null<Ui::RpWidget*> parent,
  718. std::shared_ptr<ChatHelpers::Show> show,
  719. PremiumFeature section,
  720. Fn<void()> readyCallback) {
  721. const auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
  722. result->show();
  723. parent->sizeValue(
  724. ) | rpl::start_with_next([=](QSize size) {
  725. result->setGeometry(QRect(QPoint(), size));
  726. }, result->lifetime());
  727. auto &lifetime = result->lifetime();
  728. struct State {
  729. std::vector<std::shared_ptr<Data::DocumentMedia>> medias;
  730. Ui::RpWidget *single = nullptr;
  731. };
  732. const auto session = &show->session();
  733. const auto state = lifetime.make_state<State>();
  734. const auto create = [=] {
  735. const auto document = LookupVideo(session, section);
  736. if (!document) {
  737. return;
  738. }
  739. state->single = VideoPreview(
  740. result,
  741. show,
  742. document,
  743. !VideoAlignToTop(section),
  744. readyCallback);
  745. };
  746. create();
  747. if (!state->single) {
  748. session->api().premium().videosUpdated(
  749. ) | rpl::take(1) | rpl::start_with_next(create, lifetime);
  750. }
  751. return result;
  752. }
  753. [[nodiscard]] not_null<Ui::RpWidget*> GenerateDefaultPreview(
  754. not_null<Ui::RpWidget*> parent,
  755. std::shared_ptr<ChatHelpers::Show> show,
  756. PremiumFeature section,
  757. Fn<void()> readyCallback) {
  758. switch (section) {
  759. case PremiumFeature::Stickers:
  760. return StickersPreview(parent, std::move(show), readyCallback);
  761. default:
  762. return GenericPreview(
  763. parent,
  764. std::move(show),
  765. section,
  766. readyCallback);
  767. }
  768. }
  769. [[nodiscard]] object_ptr<Ui::GradientButton> CreateGradientButton(
  770. QWidget *parent,
  771. QGradientStops stops) {
  772. return object_ptr<Ui::GradientButton>(parent, std::move(stops));
  773. }
  774. [[nodiscard]] object_ptr<Ui::GradientButton> CreatePremiumButton(
  775. QWidget *parent) {
  776. return CreateGradientButton(parent, Ui::Premium::ButtonGradientStops());
  777. }
  778. [[nodiscard]] object_ptr<Ui::RpWidget> CreateSwitch(
  779. not_null<Ui::RpWidget*> parent,
  780. not_null<rpl::variable<PremiumFeature>*> selected,
  781. std::vector<PremiumFeature> order) {
  782. const auto padding = st::premiumDotPadding;
  783. const auto width = padding.left() + st::premiumDot + padding.right();
  784. const auto height = padding.top() + st::premiumDot + padding.bottom();
  785. const auto stops = Ui::Premium::ButtonGradientStops();
  786. auto result = object_ptr<Ui::FixedHeightWidget>(parent.get(), height);
  787. const auto raw = result.data();
  788. const auto count = order.size();
  789. for (auto i = 0; i != count; ++i) {
  790. const auto section = order[i];
  791. const auto button = Ui::CreateChild<Ui::AbstractButton>(raw);
  792. parent->widthValue(
  793. ) | rpl::start_with_next([=](int outer) {
  794. const auto full = width * count;
  795. const auto left = (outer - full) / 2 + (i * width);
  796. button->setGeometry(left, 0, width, height);
  797. }, button->lifetime());
  798. button->setClickedCallback([=] {
  799. *selected = section;
  800. });
  801. button->paintRequest(
  802. ) | rpl::start_with_next([=] {
  803. auto p = QPainter(button);
  804. auto hq = PainterHighQualityEnabler(p);
  805. p.setBrush((selected->current() == section)
  806. ? anim::gradient_color_at(
  807. stops,
  808. float64(i) / (count - 1))
  809. : st::windowBgRipple->c);
  810. p.setPen(Qt::NoPen);
  811. p.drawEllipse(
  812. button->rect().marginsRemoved(st::premiumDotPadding));
  813. }, button->lifetime());
  814. selected->changes(
  815. ) | rpl::start_with_next([=] {
  816. button->update();
  817. }, button->lifetime());
  818. }
  819. return result;
  820. }
  821. void PreviewBox(
  822. not_null<Ui::GenericBox*> box,
  823. std::shared_ptr<ChatHelpers::Show> show,
  824. const Descriptor &descriptor,
  825. const std::shared_ptr<Data::DocumentMedia> &media,
  826. const QImage &back) {
  827. const auto single = st::boxWideWidth;
  828. const auto size = QSize(single, st::premiumPreviewHeight);
  829. box->setWidth(size.width());
  830. box->setNoContentMargin(true);
  831. const auto outer = box->addRow(
  832. ChatBackPreview(box, size.height(), back),
  833. {});
  834. struct Hiding {
  835. not_null<Ui::RpWidget*> widget;
  836. int leftFrom = 0;
  837. int leftTill = 0;
  838. };
  839. struct State {
  840. int leftFrom = 0;
  841. Ui::RpWidget *content = nullptr;
  842. Ui::RpWidget *stickersPreload = nullptr;
  843. bool stickersPreloadReady = false;
  844. bool preloadScheduled = false;
  845. bool showFinished = false;
  846. Ui::Animations::Simple animation;
  847. Fn<void()> preload;
  848. std::vector<Hiding> hiding;
  849. rpl::variable<PremiumFeature> selected;
  850. std::vector<PremiumFeature> order;
  851. };
  852. const auto state = outer->lifetime().make_state<State>();
  853. state->selected = descriptor.section;
  854. auto premiumOrder = Settings::PremiumFeaturesOrder(&show->session());
  855. auto businessOrder = Settings::BusinessFeaturesOrder(&show->session());
  856. state->order = ranges::contains(businessOrder, descriptor.section)
  857. ? std::move(businessOrder)
  858. : ranges::contains(premiumOrder, descriptor.section)
  859. ? std::move(premiumOrder)
  860. : std::vector{ descriptor.section };
  861. const auto index = [=](PremiumFeature section) {
  862. const auto it = ranges::find(state->order, section);
  863. return (it == end(state->order))
  864. ? 0
  865. : std::distance(begin(state->order), it);
  866. };
  867. const auto move = [=](int delta) {
  868. const auto count = int(state->order.size());
  869. const auto now = state->selected.current();
  870. state->selected = state->order[(index(now) + count + delta) % count];
  871. };
  872. const auto buttonsParent = box->verticalLayout().get();
  873. const auto close = Ui::CreateChild<Ui::IconButton>(
  874. buttonsParent,
  875. st::settingsPremiumTopBarClose);
  876. close->setClickedCallback([=] { box->closeBox(); });
  877. const auto left = Ui::CreateChild<Ui::IconButton>(
  878. buttonsParent,
  879. st::settingsPremiumMoveLeft);
  880. left->setClickedCallback([=] { move(-1); });
  881. const auto right = Ui::CreateChild<Ui::IconButton>(
  882. buttonsParent,
  883. st::settingsPremiumMoveRight);
  884. right->setClickedCallback([=] { move(1); });
  885. buttonsParent->widthValue(
  886. ) | rpl::start_with_next([=](int width) {
  887. const auto outerHeight = st::premiumPreviewHeight;
  888. close->moveToRight(0, 0, width);
  889. left->moveToLeft(0, (outerHeight - left->height()) / 2, width);
  890. right->moveToRight(0, (outerHeight - right->height()) / 2, width);
  891. }, close->lifetime());
  892. state->preload = [=] {
  893. if (!state->showFinished) {
  894. state->preloadScheduled = true;
  895. return;
  896. }
  897. const auto now = state->selected.current();
  898. if (now != PremiumFeature::Stickers && !state->stickersPreload) {
  899. const auto ready = [=] {
  900. if (state->stickersPreload) {
  901. state->stickersPreloadReady = true;
  902. } else {
  903. state->preload();
  904. }
  905. };
  906. state->stickersPreload = GenerateDefaultPreview(
  907. outer,
  908. show,
  909. PremiumFeature::Stickers,
  910. ready);
  911. state->stickersPreload->hide();
  912. }
  913. };
  914. switch (descriptor.section) {
  915. case PremiumFeature::Stickers:
  916. state->content = media
  917. ? StickerPreview(outer, show, media, state->preload)
  918. : StickersPreview(outer, show, state->preload);
  919. break;
  920. default:
  921. state->content = GenericPreview(
  922. outer,
  923. show,
  924. descriptor.section,
  925. state->preload);
  926. break;
  927. }
  928. state->selected.value(
  929. ) | rpl::combine_previous(
  930. ) | rpl::start_with_next([=](PremiumFeature was, PremiumFeature now) {
  931. const auto animationCallback = [=] {
  932. if (!state->animation.animating()) {
  933. for (const auto &hiding : base::take(state->hiding)) {
  934. delete hiding.widget;
  935. }
  936. state->leftFrom = 0;
  937. state->content->move(0, 0);
  938. } else {
  939. const auto progress = state->animation.value(1.);
  940. state->content->move(
  941. anim::interpolate(state->leftFrom, 0, progress),
  942. 0);
  943. for (const auto &hiding : state->hiding) {
  944. hiding.widget->move(anim::interpolate(
  945. hiding.leftFrom,
  946. hiding.leftTill,
  947. progress), 0);
  948. }
  949. }
  950. };
  951. animationCallback();
  952. const auto toLeft = index(now) > index(was);
  953. auto start = state->content->x() + (toLeft ? single : -single);
  954. for (const auto &hiding : state->hiding) {
  955. const auto left = hiding.widget->x();
  956. if (toLeft && left + single > start) {
  957. start = left + single;
  958. } else if (!toLeft && left - single < start) {
  959. start = left - single;
  960. }
  961. }
  962. for (auto &hiding : state->hiding) {
  963. hiding.leftFrom = hiding.widget->x();
  964. hiding.leftTill = hiding.leftFrom - start;
  965. }
  966. state->hiding.push_back({
  967. .widget = state->content,
  968. .leftFrom = state->content->x(),
  969. .leftTill = state->content->x() - start,
  970. });
  971. state->leftFrom = start;
  972. if (now == PremiumFeature::Stickers && state->stickersPreload) {
  973. state->content = base::take(state->stickersPreload);
  974. state->content->show();
  975. if (base::take(state->stickersPreloadReady)) {
  976. state->preload();
  977. }
  978. } else {
  979. state->content = GenerateDefaultPreview(
  980. outer,
  981. show,
  982. now,
  983. state->preload);
  984. }
  985. state->animation.stop();
  986. state->animation.start(
  987. animationCallback,
  988. 0.,
  989. 1.,
  990. st::premiumSlideDuration,
  991. anim::sineInOut);
  992. }, outer->lifetime());
  993. auto title = state->selected.value(
  994. ) | rpl::map(SectionTitle) | rpl::flatten_latest();
  995. auto text = state->selected.value(
  996. ) | rpl::map(SectionAbout) | rpl::flatten_latest();
  997. const auto padding = st::premiumPreviewAboutPadding;
  998. const auto available = size.width() - padding.left() - padding.right();
  999. auto titleLabel = object_ptr<Ui::FlatLabel>(
  1000. box,
  1001. std::move(title),
  1002. st::premiumPreviewAboutTitle);
  1003. titleLabel->resizeToWidth(available);
  1004. box->addRow(
  1005. object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
  1006. box,
  1007. std::move(titleLabel)),
  1008. st::premiumPreviewAboutTitlePadding);
  1009. auto textLabel = object_ptr<Ui::FlatLabel>(
  1010. box,
  1011. std::move(text),
  1012. st::premiumPreviewAbout);
  1013. textLabel->resizeToWidth(available);
  1014. box->addRow(
  1015. object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(box, std::move(textLabel)),
  1016. padding);
  1017. box->addRow(
  1018. CreateSwitch(box->verticalLayout(), &state->selected, state->order),
  1019. st::premiumDotsMargin);
  1020. const auto showFinished = [=] {
  1021. state->showFinished = true;
  1022. if (base::take(state->preloadScheduled)) {
  1023. state->preload();
  1024. }
  1025. };
  1026. if ((descriptor.fromSettings && show->session().premium())
  1027. || descriptor.hideSubscriptionButton) {
  1028. box->setShowFinishedCallback(showFinished);
  1029. box->addButton(tr::lng_close(), [=] { box->closeBox(); });
  1030. } else {
  1031. box->setStyle(st::premiumPreviewBox);
  1032. const auto buttonPadding = st::premiumPreviewBox.buttonPadding;
  1033. const auto width = size.width()
  1034. - buttonPadding.left()
  1035. - buttonPadding.right();
  1036. const auto computeRef = [=] {
  1037. return Settings::LookupPremiumRef(state->selected.current());
  1038. };
  1039. auto unlock = state->selected.value(
  1040. ) | rpl::map([=](PremiumFeature section) {
  1041. return (section == PremiumFeature::InfiniteReactions)
  1042. ? tr::lng_premium_unlock_reactions()
  1043. : (section == PremiumFeature::Stickers)
  1044. ? tr::lng_premium_unlock_stickers()
  1045. : (section == PremiumFeature::AnimatedEmoji)
  1046. ? tr::lng_premium_unlock_emoji()
  1047. : (section == PremiumFeature::EmojiStatus)
  1048. ? tr::lng_premium_unlock_status()
  1049. : tr::lng_premium_more_about();
  1050. }) | rpl::flatten_latest();
  1051. auto button = descriptor.fromSettings
  1052. ? object_ptr<Ui::GradientButton>::fromRaw(
  1053. Settings::CreateSubscribeButton({
  1054. .parent = box,
  1055. .computeRef = computeRef,
  1056. .show = show,
  1057. }))
  1058. : CreateUnlockButton(box, std::move(unlock));
  1059. button->resizeToWidth(width);
  1060. if (!descriptor.fromSettings) {
  1061. button->setClickedCallback([=] {
  1062. const auto window = show->resolveWindow();
  1063. if (!window) {
  1064. return;
  1065. }
  1066. Settings::ShowPremium(
  1067. window,
  1068. Settings::LookupPremiumRef(state->selected.current()));
  1069. });
  1070. }
  1071. box->setShowFinishedCallback([=, raw = button.data()]{
  1072. showFinished();
  1073. raw->startGlareAnimation();
  1074. });
  1075. box->addButton(std::move(button));
  1076. }
  1077. if (descriptor.fromSettings) {
  1078. Data::AmPremiumValue(
  1079. &show->session()
  1080. ) | rpl::skip(1) | rpl::start_with_next([=] {
  1081. box->closeBox();
  1082. }, box->lifetime());
  1083. }
  1084. box->events(
  1085. ) | rpl::start_with_next([=](not_null<QEvent*> e) {
  1086. if (e->type() == QEvent::KeyPress) {
  1087. const auto key = static_cast<QKeyEvent*>(e.get())->key();
  1088. if (key == Qt::Key_Left) {
  1089. move(-1);
  1090. } else if (key == Qt::Key_Right) {
  1091. move(1);
  1092. }
  1093. }
  1094. }, box->lifetime());
  1095. if (const auto &hidden = descriptor.hiddenCallback) {
  1096. box->boxClosing() | rpl::start_with_next(hidden, box->lifetime());
  1097. }
  1098. }
  1099. void Show(
  1100. std::shared_ptr<ChatHelpers::Show> show,
  1101. const Descriptor &descriptor,
  1102. const std::shared_ptr<Data::DocumentMedia> &media,
  1103. QImage back) {
  1104. auto box = Box(PreviewBox, show, descriptor, media, back);
  1105. const auto raw = box.data();
  1106. show->showBox(std::move(box));
  1107. if (descriptor.shownCallback) {
  1108. descriptor.shownCallback(raw);
  1109. }
  1110. }
  1111. void Show(std::shared_ptr<ChatHelpers::Show> show, QImage back) {
  1112. auto &list = Preloads();
  1113. for (auto i = begin(list); i != end(list);) {
  1114. const auto already = i->show.lock();
  1115. if (!already) {
  1116. i = list.erase(i);
  1117. } else if (already == show) {
  1118. Show(std::move(show), i->descriptor, i->media, back);
  1119. i = list.erase(i);
  1120. return;
  1121. } else {
  1122. ++i;
  1123. }
  1124. }
  1125. }
  1126. void DecorateListPromoBox(
  1127. not_null<Ui::GenericBox*> box,
  1128. std::shared_ptr<ChatHelpers::Show> show,
  1129. const Descriptor &descriptor) {
  1130. const auto session = &show->session();
  1131. box->addTopButton(st::boxTitleClose, [=] {
  1132. box->closeBox();
  1133. });
  1134. if (!descriptor.hideSubscriptionButton) {
  1135. Data::AmPremiumValue(
  1136. session
  1137. ) | rpl::skip(1) | rpl::start_with_next([=] {
  1138. box->closeBox();
  1139. }, box->lifetime());
  1140. }
  1141. if (const auto &hidden = descriptor.hiddenCallback) {
  1142. box->boxClosing() | rpl::start_with_next(hidden, box->lifetime());
  1143. }
  1144. if (session->premium() || descriptor.hideSubscriptionButton) {
  1145. box->addButton(tr::lng_close(), [=] {
  1146. box->closeBox();
  1147. });
  1148. } else {
  1149. const auto button = Settings::CreateSubscribeButton({
  1150. .parent = box,
  1151. .computeRef = [] { return u"double_limits"_q; },
  1152. .show = show,
  1153. });
  1154. box->setShowFinishedCallback([=] {
  1155. button->startGlareAnimation();
  1156. });
  1157. box->setStyle(st::premiumPreviewDoubledLimitsBox);
  1158. box->widthValue(
  1159. ) | rpl::start_with_next([=](int width) {
  1160. const auto &padding
  1161. = st::premiumPreviewDoubledLimitsBox.buttonPadding;
  1162. button->resizeToWidth(width
  1163. - padding.left()
  1164. - padding.right());
  1165. button->moveToLeft(padding.left(), padding.top());
  1166. }, button->lifetime());
  1167. box->addButton(
  1168. object_ptr<Ui::AbstractButton>::fromRaw(button));
  1169. }
  1170. }
  1171. void Show(
  1172. std::shared_ptr<ChatHelpers::Show> show,
  1173. Descriptor &&descriptor) {
  1174. if (!show->session().premiumPossible()) {
  1175. auto box = Box(PremiumUnavailableBox);
  1176. const auto raw = box.data();
  1177. show->showBox(std::move(box));
  1178. if (descriptor.shownCallback) {
  1179. descriptor.shownCallback(raw);
  1180. }
  1181. return;
  1182. } else if (descriptor.section == PremiumFeature::DoubleLimits) {
  1183. show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
  1184. DoubledLimitsPreviewBox(box, &show->session());
  1185. DecorateListPromoBox(box, show, descriptor);
  1186. }));
  1187. return;
  1188. } else if (descriptor.section == PremiumFeature::Stories) {
  1189. show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
  1190. UpgradedStoriesPreviewBox(box, &show->session());
  1191. DecorateListPromoBox(box, show, descriptor);
  1192. }));
  1193. return;
  1194. } else if (descriptor.section == PremiumFeature::Business) {
  1195. show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
  1196. TelegramBusinessPreviewBox(box, &show->session());
  1197. DecorateListPromoBox(box, show, descriptor);
  1198. }));
  1199. return;
  1200. }
  1201. auto &list = Preloads();
  1202. for (auto i = begin(list); i != end(list);) {
  1203. const auto already = i->show.lock();
  1204. if (!already) {
  1205. i = list.erase(i);
  1206. } else if (already == show) {
  1207. if (i->descriptor == descriptor) {
  1208. return;
  1209. }
  1210. i->descriptor = descriptor;
  1211. i->media = descriptor.requestedSticker
  1212. ? descriptor.requestedSticker->createMediaView()
  1213. : nullptr;
  1214. if (const auto &media = i->media) {
  1215. PreloadSticker(media);
  1216. }
  1217. return;
  1218. } else {
  1219. ++i;
  1220. }
  1221. }
  1222. const auto weak = std::weak_ptr(show);
  1223. list.push_back({
  1224. .descriptor = descriptor,
  1225. .media = (descriptor.requestedSticker
  1226. ? descriptor.requestedSticker->createMediaView()
  1227. : nullptr),
  1228. .show = weak,
  1229. });
  1230. if (const auto &media = list.back().media) {
  1231. PreloadSticker(media);
  1232. }
  1233. const auto fill = QSize(st::boxWideWidth, st::boxWideWidth);
  1234. const auto stops = Ui::Premium::LimitGradientStops();
  1235. crl::async([=] {
  1236. const auto factor = style::DevicePixelRatio();
  1237. auto cropped = QImage(
  1238. fill * factor,
  1239. QImage::Format_ARGB32_Premultiplied);
  1240. cropped.setDevicePixelRatio(factor);
  1241. auto p = QPainter(&cropped);
  1242. auto gradient = QLinearGradient(0, fill.height(), fill.width(), 0);
  1243. gradient.setStops(stops);
  1244. p.fillRect(QRect(QPoint(), fill), gradient);
  1245. p.end();
  1246. const auto result = Images::Round(
  1247. std::move(cropped),
  1248. Images::CornersMask(st::boxRadius),
  1249. RectPart::TopLeft | RectPart::TopRight);
  1250. crl::on_main([=] {
  1251. if (auto strong = weak.lock()) {
  1252. Show(std::move(strong), result);
  1253. }
  1254. });
  1255. });
  1256. }
  1257. } // namespace
  1258. void ShowStickerPreviewBox(
  1259. std::shared_ptr<ChatHelpers::Show> show,
  1260. not_null<DocumentData*> document) {
  1261. Show(std::move(show), Descriptor{
  1262. .section = PremiumFeature::Stickers,
  1263. .requestedSticker = document,
  1264. });
  1265. }
  1266. void ShowPremiumPreviewBox(
  1267. not_null<Window::SessionController*> controller,
  1268. PremiumFeature section,
  1269. Fn<void(not_null<Ui::BoxContent*>)> shown) {
  1270. ShowPremiumPreviewBox(controller->uiShow(), section, std::move(shown));
  1271. }
  1272. void ShowPremiumPreviewBox(
  1273. std::shared_ptr<ChatHelpers::Show> show,
  1274. PremiumFeature section,
  1275. Fn<void(not_null<Ui::BoxContent*>)> shown,
  1276. bool hideSubscriptionButton) {
  1277. Show(std::move(show), Descriptor{
  1278. .section = section,
  1279. .shownCallback = std::move(shown),
  1280. .hideSubscriptionButton = hideSubscriptionButton,
  1281. });
  1282. }
  1283. void ShowPremiumPreviewToBuy(
  1284. not_null<Window::SessionController*> controller,
  1285. PremiumFeature section,
  1286. Fn<void()> hiddenCallback) {
  1287. Show(controller->uiShow(), Descriptor{
  1288. .section = section,
  1289. .fromSettings = true,
  1290. .hiddenCallback = std::move(hiddenCallback),
  1291. });
  1292. }
  1293. void PremiumUnavailableBox(not_null<Ui::GenericBox*> box) {
  1294. Ui::ConfirmBox(box, {
  1295. .text = tr::lng_premium_unavailable(
  1296. tr::now,
  1297. Ui::Text::RichLangValue),
  1298. .inform = true,
  1299. });
  1300. }
  1301. void DoubledLimitsPreviewBox(
  1302. not_null<Ui::GenericBox*> box,
  1303. not_null<Main::Session*> session) {
  1304. box->setTitle(tr::lng_premium_summary_subtitle_double_limits());
  1305. const auto limits = Data::PremiumLimits(session);
  1306. auto entries = std::vector<Ui::Premium::ListEntry>();
  1307. {
  1308. const auto premium = limits.channelsPremium();
  1309. entries.push_back({
  1310. tr::lng_premium_double_limits_subtitle_channels(),
  1311. tr::lng_premium_double_limits_about_channels(
  1312. lt_count,
  1313. rpl::single(float64(premium)),
  1314. Ui::Text::RichLangValue),
  1315. limits.channelsDefault(),
  1316. premium,
  1317. });
  1318. }
  1319. {
  1320. const auto premium = limits.dialogsPinnedPremium();
  1321. entries.push_back({
  1322. tr::lng_premium_double_limits_subtitle_pins(),
  1323. tr::lng_premium_double_limits_about_pins(
  1324. lt_count,
  1325. rpl::single(float64(premium)),
  1326. Ui::Text::RichLangValue),
  1327. limits.dialogsPinnedDefault(),
  1328. premium,
  1329. });
  1330. }
  1331. {
  1332. const auto premium = limits.channelsPublicPremium();
  1333. entries.push_back({
  1334. tr::lng_premium_double_limits_subtitle_links(),
  1335. tr::lng_premium_double_limits_about_links(
  1336. lt_count,
  1337. rpl::single(float64(premium)),
  1338. Ui::Text::RichLangValue),
  1339. limits.channelsPublicDefault(),
  1340. premium,
  1341. });
  1342. }
  1343. {
  1344. const auto premium = limits.gifsPremium();
  1345. entries.push_back({
  1346. tr::lng_premium_double_limits_subtitle_gifs(),
  1347. tr::lng_premium_double_limits_about_gifs(
  1348. lt_count,
  1349. rpl::single(float64(premium)),
  1350. Ui::Text::RichLangValue),
  1351. limits.gifsDefault(),
  1352. premium,
  1353. });
  1354. }
  1355. {
  1356. const auto premium = limits.stickersFavedPremium();
  1357. entries.push_back({
  1358. tr::lng_premium_double_limits_subtitle_stickers(),
  1359. tr::lng_premium_double_limits_about_stickers(
  1360. lt_count,
  1361. rpl::single(float64(premium)),
  1362. Ui::Text::RichLangValue),
  1363. limits.stickersFavedDefault(),
  1364. premium,
  1365. });
  1366. }
  1367. {
  1368. const auto premium = limits.aboutLengthPremium();
  1369. entries.push_back({
  1370. tr::lng_premium_double_limits_subtitle_bio(),
  1371. tr::lng_premium_double_limits_about_bio(
  1372. Ui::Text::RichLangValue),
  1373. limits.aboutLengthDefault(),
  1374. premium,
  1375. });
  1376. }
  1377. {
  1378. const auto premium = limits.captionLengthPremium();
  1379. entries.push_back({
  1380. tr::lng_premium_double_limits_subtitle_captions(),
  1381. tr::lng_premium_double_limits_about_captions(
  1382. Ui::Text::RichLangValue),
  1383. limits.captionLengthDefault(),
  1384. premium,
  1385. });
  1386. }
  1387. {
  1388. const auto premium = limits.dialogFiltersPremium();
  1389. entries.push_back({
  1390. tr::lng_premium_double_limits_subtitle_folders(),
  1391. tr::lng_premium_double_limits_about_folders(
  1392. lt_count,
  1393. rpl::single(float64(premium)),
  1394. Ui::Text::RichLangValue),
  1395. limits.dialogFiltersDefault(),
  1396. premium,
  1397. });
  1398. }
  1399. {
  1400. const auto premium = limits.dialogFiltersChatsPremium();
  1401. entries.push_back({
  1402. tr::lng_premium_double_limits_subtitle_folder_chats(),
  1403. tr::lng_premium_double_limits_about_folder_chats(
  1404. lt_count,
  1405. rpl::single(float64(premium)),
  1406. Ui::Text::RichLangValue),
  1407. limits.dialogFiltersChatsDefault(),
  1408. premium,
  1409. });
  1410. }
  1411. const auto nextMax = session->domain().maxAccounts() + 1;
  1412. const auto till = (nextMax >= Main::Domain::kPremiumMaxAccounts)
  1413. ? QString::number(Main::Domain::kPremiumMaxAccounts)
  1414. : (QString::number(nextMax) + QChar('+'));
  1415. entries.push_back({
  1416. tr::lng_premium_double_limits_subtitle_accounts(),
  1417. tr::lng_premium_double_limits_about_accounts(
  1418. lt_count,
  1419. rpl::single(float64(Main::Domain::kPremiumMaxAccounts)),
  1420. Ui::Text::RichLangValue),
  1421. Main::Domain::kMaxAccounts,
  1422. Main::Domain::kPremiumMaxAccounts,
  1423. till,
  1424. });
  1425. {
  1426. const auto premium = limits.similarChannelsPremium();
  1427. entries.push_back({
  1428. tr::lng_premium_double_limits_subtitle_similar_channels(),
  1429. tr::lng_premium_double_limits_about_similar_channels(
  1430. lt_count,
  1431. rpl::single(float64(premium)),
  1432. Ui::Text::RichLangValue),
  1433. limits.similarChannelsDefault(),
  1434. premium,
  1435. });
  1436. }
  1437. Ui::Premium::ShowListBox(
  1438. box,
  1439. st::defaultPremiumLimits,
  1440. std::move(entries));
  1441. }
  1442. void UpgradedStoriesPreviewBox(
  1443. not_null<Ui::GenericBox*> box,
  1444. not_null<Main::Session*> session) {
  1445. using namespace Ui::Text;
  1446. box->setTitle(tr::lng_premium_summary_subtitle_stories());
  1447. auto entries = std::vector<Ui::Premium::ListEntry>();
  1448. entries.push_back({
  1449. .title = tr::lng_premium_stories_subtitle_order(),
  1450. .about = tr::lng_premium_stories_about_order(WithEntities),
  1451. .icon = &st::settingsStoriesIconOrder,
  1452. });
  1453. entries.push_back({
  1454. .title = tr::lng_premium_stories_subtitle_stealth(),
  1455. .about = tr::lng_premium_stories_about_stealth(WithEntities),
  1456. .icon = &st::settingsStoriesIconStealth,
  1457. });
  1458. entries.push_back({
  1459. .title = tr::lng_premium_stories_subtitle_views(),
  1460. .about = tr::lng_premium_stories_about_views(WithEntities),
  1461. .icon = &st::settingsStoriesIconViews,
  1462. });
  1463. entries.push_back({
  1464. .title = tr::lng_premium_stories_subtitle_expiration(),
  1465. .about = tr::lng_premium_stories_about_expiration(WithEntities),
  1466. .icon = &st::settingsStoriesIconExpiration,
  1467. });
  1468. entries.push_back({
  1469. .title = tr::lng_premium_stories_subtitle_download(),
  1470. .about = tr::lng_premium_stories_about_download(WithEntities),
  1471. .icon = &st::settingsStoriesIconDownload,
  1472. });
  1473. entries.push_back({
  1474. .title = tr::lng_premium_stories_subtitle_caption(),
  1475. .about = tr::lng_premium_stories_about_caption(WithEntities),
  1476. .icon = &st::settingsStoriesIconCaption,
  1477. });
  1478. entries.push_back({
  1479. .title = tr::lng_premium_stories_subtitle_links(),
  1480. .about = tr::lng_premium_stories_about_links(WithEntities),
  1481. .icon = &st::settingsStoriesIconLinks,
  1482. });
  1483. Ui::Premium::ShowListBox(
  1484. box,
  1485. st::defaultPremiumLimits,
  1486. std::move(entries));
  1487. Ui::AddDividerText(
  1488. box->verticalLayout(),
  1489. tr::lng_premium_stories_about_mobile());
  1490. }
  1491. void TelegramBusinessPreviewBox(
  1492. not_null<Ui::GenericBox*> box,
  1493. not_null<Main::Session*> session) {
  1494. using namespace Ui::Text;
  1495. box->setTitle(tr::lng_business_title());
  1496. auto entries = std::vector<Ui::Premium::ListEntry>();
  1497. const auto push = [&](
  1498. tr::phrase<> title,
  1499. tr::phrase<> description,
  1500. const style::icon &icon) {
  1501. entries.push_back({
  1502. .title = title(),
  1503. .about = description(WithEntities),
  1504. .icon = &icon,
  1505. });
  1506. };
  1507. for (const auto feature : Settings::BusinessFeaturesOrder(session)) {
  1508. switch (feature) {
  1509. case PremiumFeature::GreetingMessage: push(
  1510. tr::lng_business_subtitle_greeting_messages,
  1511. tr::lng_business_about_greeting_messages,
  1512. st::settingsBusinessPromoGreeting);
  1513. break;
  1514. case PremiumFeature::AwayMessage: push(
  1515. tr::lng_business_subtitle_away_messages,
  1516. tr::lng_business_about_away_messages,
  1517. st::settingsBusinessPromoAway);
  1518. break;
  1519. case PremiumFeature::QuickReplies: push(
  1520. tr::lng_business_subtitle_quick_replies,
  1521. tr::lng_business_about_quick_replies,
  1522. st::settingsBusinessPromoReplies);
  1523. break;
  1524. case PremiumFeature::BusinessHours: push(
  1525. tr::lng_business_subtitle_opening_hours,
  1526. tr::lng_business_about_opening_hours,
  1527. st::settingsBusinessPromoHours);
  1528. break;
  1529. case PremiumFeature::BusinessLocation: push(
  1530. tr::lng_business_subtitle_location,
  1531. tr::lng_business_about_location,
  1532. st::settingsBusinessPromoLocation);
  1533. break;
  1534. case PremiumFeature::BusinessBots: push(
  1535. tr::lng_business_subtitle_chatbots,
  1536. tr::lng_business_about_chatbots,
  1537. st::settingsBusinessPromoChatbots);
  1538. break;
  1539. case PremiumFeature::ChatIntro: push(
  1540. tr::lng_business_subtitle_chat_intro,
  1541. tr::lng_business_about_chat_intro,
  1542. st::settingsBusinessPromoChatIntro);
  1543. break;
  1544. case PremiumFeature::ChatLinks: push(
  1545. tr::lng_business_subtitle_chat_links,
  1546. tr::lng_business_about_chat_links,
  1547. st::settingsBusinessPromoChatLinks);
  1548. break;
  1549. case PremiumFeature::FilterTags: push(
  1550. tr::lng_premium_summary_subtitle_filter_tags,
  1551. tr::lng_premium_summary_about_filter_tags,
  1552. st::settingsPremiumIconTags);
  1553. break;
  1554. }
  1555. }
  1556. Ui::Premium::ShowListBox(
  1557. box,
  1558. st::defaultPremiumLimits,
  1559. std::move(entries));
  1560. }
  1561. object_ptr<Ui::GradientButton> CreateUnlockButton(
  1562. QWidget *parent,
  1563. rpl::producer<QString> text) {
  1564. auto result = CreatePremiumButton(parent);
  1565. const auto &st = st::premiumPreviewBox.button;
  1566. result->resize(result->width(), st.height);
  1567. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  1568. result.data(),
  1569. std::move(text),
  1570. st::premiumPreviewButtonLabel);
  1571. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  1572. rpl::combine(
  1573. result->widthValue(),
  1574. label->widthValue()
  1575. ) | rpl::start_with_next([=](int outer, int width) {
  1576. label->moveToLeft(
  1577. (outer - width) / 2,
  1578. st::premiumPreviewBox.button.textTop,
  1579. outer);
  1580. }, label->lifetime());
  1581. return result;
  1582. }