star_gift_box.cpp 89 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138
  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/star_gift_box.h"
  8. #include "apiwrap.h"
  9. #include "api/api_credits.h"
  10. #include "api/api_premium.h"
  11. #include "base/event_filter.h"
  12. #include "base/random.h"
  13. #include "base/timer_rpl.h"
  14. #include "base/unixtime.h"
  15. #include "boxes/filters/edit_filter_chats_list.h"
  16. #include "boxes/peers/edit_peer_color_box.h"
  17. #include "boxes/gift_premium_box.h"
  18. #include "boxes/peer_list_controllers.h"
  19. #include "boxes/premium_preview_box.h"
  20. #include "boxes/send_credits_box.h"
  21. #include "chat_helpers/emoji_suggestions_widget.h"
  22. #include "chat_helpers/message_field.h"
  23. #include "chat_helpers/stickers_gift_box_pack.h"
  24. #include "chat_helpers/stickers_lottie.h"
  25. #include "chat_helpers/tabbed_panel.h"
  26. #include "chat_helpers/tabbed_selector.h"
  27. #include "core/ui_integration.h"
  28. #include "data/data_changes.h"
  29. #include "data/data_channel.h"
  30. #include "data/data_credits.h"
  31. #include "data/data_document.h"
  32. #include "data/data_document_media.h"
  33. #include "data/data_emoji_statuses.h"
  34. #include "data/data_file_origin.h"
  35. #include "data/data_peer_values.h"
  36. #include "data/data_premium_limits.h"
  37. #include "data/data_session.h"
  38. #include "data/data_user.h"
  39. #include "data/stickers/data_custom_emoji.h"
  40. #include "history/admin_log/history_admin_log_item.h"
  41. #include "history/view/media/history_view_media_generic.h"
  42. #include "history/view/media/history_view_unique_gift.h"
  43. #include "history/view/history_view_element.h"
  44. #include "history/history.h"
  45. #include "history/history_item.h"
  46. #include "history/history_item_helpers.h"
  47. #include "info/peer_gifts/info_peer_gifts_common.h"
  48. #include "info/profile/info_profile_icon.h"
  49. #include "lang/lang_keys.h"
  50. #include "lottie/lottie_common.h"
  51. #include "lottie/lottie_single_player.h"
  52. #include "main/main_app_config.h"
  53. #include "main/main_session.h"
  54. #include "payments/payments_form.h"
  55. #include "payments/payments_checkout_process.h"
  56. #include "payments/payments_non_panel_process.h"
  57. #include "settings/settings_credits.h"
  58. #include "settings/settings_credits_graphics.h"
  59. #include "settings/settings_premium.h"
  60. #include "ui/boxes/boost_box.h"
  61. #include "ui/chat/chat_style.h"
  62. #include "ui/chat/chat_theme.h"
  63. #include "ui/controls/emoji_button.h"
  64. #include "ui/controls/userpic_button.h"
  65. #include "ui/effects/path_shift_gradient.h"
  66. #include "ui/effects/premium_graphics.h"
  67. #include "ui/effects/premium_stars_colored.h"
  68. #include "ui/layers/generic_box.h"
  69. #include "ui/new_badges.h"
  70. #include "ui/painter.h"
  71. #include "ui/rect.h"
  72. #include "ui/text/format_values.h"
  73. #include "ui/text/text_utilities.h"
  74. #include "ui/toast/toast.h"
  75. #include "ui/ui_utility.h"
  76. #include "ui/vertical_list.h"
  77. #include "ui/widgets/fields/input_field.h"
  78. #include "ui/widgets/buttons.h"
  79. #include "ui/widgets/checkbox.h"
  80. #include "ui/widgets/popup_menu.h"
  81. #include "ui/widgets/shadow.h"
  82. #include "ui/wrap/slide_wrap.h"
  83. #include "window/themes/window_theme.h"
  84. #include "window/section_widget.h"
  85. #include "window/window_session_controller.h"
  86. #include "styles/style_boxes.h"
  87. #include "styles/style_chat.h"
  88. #include "styles/style_chat_helpers.h"
  89. #include "styles/style_credits.h"
  90. #include "styles/style_layers.h"
  91. #include "styles/style_menu_icons.h"
  92. #include "styles/style_premium.h"
  93. #include "styles/style_settings.h"
  94. #include "styles/style_widgets.h"
  95. #include <QtWidgets/QApplication>
  96. namespace Ui {
  97. namespace {
  98. constexpr auto kPriceTabAll = 0;
  99. constexpr auto kPriceTabLimited = -2;
  100. constexpr auto kPriceTabInStock = -1;
  101. constexpr auto kGiftMessageLimit = 255;
  102. constexpr auto kSentToastDuration = 3 * crl::time(1000);
  103. constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
  104. constexpr auto kCrossfadeDuration = crl::time(400);
  105. constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000);
  106. constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000);
  107. using namespace HistoryView;
  108. using namespace Info::PeerGifts;
  109. struct PremiumGiftsDescriptor {
  110. std::vector<GiftTypePremium> list;
  111. std::shared_ptr<Api::PremiumGiftCodeOptions> api;
  112. };
  113. struct GiftsDescriptor {
  114. std::vector<GiftDescriptor> list;
  115. std::shared_ptr<Api::PremiumGiftCodeOptions> api;
  116. };
  117. struct GiftDetails {
  118. GiftDescriptor descriptor;
  119. TextWithEntities text;
  120. uint64 randomId = 0;
  121. bool anonymous = false;
  122. bool upgraded = false;
  123. bool byStars = false;
  124. };
  125. class PreviewDelegate final : public DefaultElementDelegate {
  126. public:
  127. PreviewDelegate(
  128. not_null<QWidget*> parent,
  129. not_null<ChatStyle*> st,
  130. Fn<void()> update);
  131. bool elementAnimationsPaused() override;
  132. not_null<PathShiftGradient*> elementPathShiftGradient() override;
  133. Context elementContext() override;
  134. private:
  135. const not_null<QWidget*> _parent;
  136. const std::unique_ptr<PathShiftGradient> _pathGradient;
  137. };
  138. class PreviewWrap final : public RpWidget {
  139. public:
  140. PreviewWrap(
  141. not_null<QWidget*> parent,
  142. not_null<PeerData*> recipient,
  143. rpl::producer<GiftDetails> details);
  144. ~PreviewWrap();
  145. private:
  146. void paintEvent(QPaintEvent *e) override;
  147. void resizeTo(int width);
  148. void prepare(rpl::producer<GiftDetails> details);
  149. const not_null<History*> _history;
  150. const not_null<PeerData*> _recipient;
  151. const std::unique_ptr<ChatTheme> _theme;
  152. const std::unique_ptr<ChatStyle> _style;
  153. const std::unique_ptr<PreviewDelegate> _delegate;
  154. AdminLog::OwnedItem _item;
  155. QPoint _position;
  156. };
  157. [[nodiscard]] bool SortForBirthday(not_null<PeerData*> peer) {
  158. const auto user = peer->asUser();
  159. if (!user) {
  160. return false;
  161. }
  162. const auto birthday = user->birthday();
  163. if (!birthday) {
  164. return false;
  165. }
  166. const auto is = [&](const QDate &date) {
  167. return (date.day() == birthday.day())
  168. && (date.month() == birthday.month());
  169. };
  170. const auto now = QDate::currentDate();
  171. return is(now) || is(now.addDays(1)) || is(now.addDays(-1));
  172. }
  173. [[nodiscard]] bool IsSoldOut(const Data::StarGift &info) {
  174. return info.limitedCount && !info.limitedLeft;
  175. }
  176. PreviewDelegate::PreviewDelegate(
  177. not_null<QWidget*> parent,
  178. not_null<ChatStyle*> st,
  179. Fn<void()> update)
  180. : _parent(parent)
  181. , _pathGradient(MakePathShiftGradient(st, update)) {
  182. }
  183. bool PreviewDelegate::elementAnimationsPaused() {
  184. return _parent->window()->isActiveWindow();
  185. }
  186. auto PreviewDelegate::elementPathShiftGradient()
  187. -> not_null<PathShiftGradient*> {
  188. return _pathGradient.get();
  189. }
  190. Context PreviewDelegate::elementContext() {
  191. return Context::History;
  192. }
  193. auto GenerateGiftMedia(
  194. not_null<Element*> parent,
  195. Element *replacing,
  196. not_null<PeerData*> recipient,
  197. const GiftDetails &data)
  198. -> Fn<void(
  199. not_null<MediaGeneric*>,
  200. Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
  201. return [=](
  202. not_null<MediaGeneric*> media,
  203. Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
  204. const auto &descriptor = data.descriptor;
  205. auto pushText = [&](
  206. TextWithEntities text,
  207. QMargins margins = {},
  208. const base::flat_map<uint16, ClickHandlerPtr> &links = {},
  209. Ui::Text::MarkedContext context = {}) {
  210. if (text.empty()) {
  211. return;
  212. }
  213. push(std::make_unique<MediaGenericTextPart>(
  214. std::move(text),
  215. margins,
  216. st::defaultTextStyle,
  217. links,
  218. std::move(context)));
  219. };
  220. const auto sticker = [=] {
  221. using Tag = ChatHelpers::StickerLottieSize;
  222. const auto session = &parent->history()->session();
  223. const auto sticker = LookupGiftSticker(session, descriptor);
  224. return StickerInBubblePart::Data{
  225. .sticker = sticker,
  226. .size = st::chatIntroStickerSize,
  227. .cacheTag = Tag::ChatIntroHelloSticker,
  228. .singleTimePlayback = v::is<GiftTypePremium>(descriptor),
  229. };
  230. };
  231. push(std::make_unique<StickerInBubblePart>(
  232. parent,
  233. replacing,
  234. sticker,
  235. st::giftBoxPreviewStickerPadding));
  236. auto title = v::match(descriptor, [&](GiftTypePremium gift) {
  237. return tr::lng_action_gift_premium_months(
  238. tr::now,
  239. lt_count,
  240. gift.months,
  241. Text::Bold);
  242. }, [&](const GiftTypeStars &gift) {
  243. return recipient->isSelf()
  244. ? tr::lng_action_gift_self_subtitle(tr::now, Text::Bold)
  245. : tr::lng_action_gift_got_subtitle(
  246. tr::now,
  247. lt_user,
  248. TextWithEntities()
  249. .append(Text::SingleCustomEmoji(
  250. recipient->owner().customEmojiManager(
  251. ).peerUserpicEmojiData(
  252. recipient->session().user())))
  253. .append(' ')
  254. .append(recipient->session().user()->shortName()),
  255. Text::Bold);
  256. });
  257. auto textFallback = v::match(descriptor, [&](GiftTypePremium gift) {
  258. return tr::lng_action_gift_premium_about(
  259. tr::now,
  260. Text::RichLangValue);
  261. }, [&](const GiftTypeStars &gift) {
  262. return data.upgraded
  263. ? tr::lng_action_gift_got_upgradable_text(
  264. tr::now,
  265. Text::RichLangValue)
  266. : (recipient->isSelf() && gift.info.starsToUpgrade)
  267. ? tr::lng_action_gift_self_about_unique(
  268. tr::now,
  269. Text::RichLangValue)
  270. : (recipient->isBroadcast() && gift.info.starsToUpgrade)
  271. ? tr::lng_action_gift_channel_about_unique(
  272. tr::now,
  273. Text::RichLangValue)
  274. : (recipient->isSelf()
  275. ? tr::lng_action_gift_self_about
  276. : recipient->isBroadcast()
  277. ? tr::lng_action_gift_channel_about
  278. : tr::lng_action_gift_got_stars_text)(
  279. tr::now,
  280. lt_count,
  281. gift.info.starsConverted,
  282. Text::RichLangValue);
  283. });
  284. auto description = data.text.empty()
  285. ? std::move(textFallback)
  286. : data.text;
  287. const auto context = Core::TextContext({
  288. .session = &parent->history()->session(),
  289. .repaint = [parent] { parent->repaint(); },
  290. });
  291. pushText(
  292. std::move(title),
  293. st::giftBoxPreviewTitlePadding,
  294. {},
  295. context);
  296. pushText(
  297. std::move(description),
  298. st::giftBoxPreviewTextPadding,
  299. {},
  300. context);
  301. push(HistoryView::MakeGenericButtonPart(
  302. (data.upgraded
  303. ? tr::lng_gift_view_unpack(tr::now)
  304. : tr::lng_sticker_premium_view(tr::now)),
  305. st::giftBoxButtonMargin,
  306. [parent] { parent->repaint(); },
  307. nullptr));
  308. };
  309. }
  310. [[nodiscard]] QImage CreateGradient(
  311. QSize size,
  312. const Data::UniqueGift &gift) {
  313. const auto ratio = style::DevicePixelRatio();
  314. auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
  315. result.setDevicePixelRatio(ratio);
  316. auto p = QPainter(&result);
  317. auto hq = PainterHighQualityEnabler(p);
  318. auto gradient = QRadialGradient(
  319. QRect(QPoint(), size).center(),
  320. size.height() / 2);
  321. gradient.setStops({
  322. { 0., gift.backdrop.centerColor },
  323. { 1., gift.backdrop.edgeColor },
  324. });
  325. p.setBrush(gradient);
  326. p.setPen(Qt::NoPen);
  327. p.drawRect(QRect(QPoint(), size));
  328. p.end();
  329. const auto mask = Images::CornersMask(st::boxRadius);
  330. return Images::Round(std::move(result), mask, RectPart::FullTop);
  331. }
  332. void PrepareImage(
  333. QImage &image,
  334. not_null<Text::CustomEmoji*> emoji,
  335. const PatternPoint &point,
  336. const Data::UniqueGift &gift) {
  337. if (!image.isNull() || !emoji->ready()) {
  338. return;
  339. }
  340. const auto ratio = style::DevicePixelRatio();
  341. const auto size = Emoji::GetSizeNormal() / ratio;
  342. image = QImage(
  343. 2 * QSize(size, size) * ratio,
  344. QImage::Format_ARGB32_Premultiplied);
  345. image.setDevicePixelRatio(ratio);
  346. image.fill(Qt::transparent);
  347. auto p = QPainter(&image);
  348. auto hq = PainterHighQualityEnabler(p);
  349. p.setOpacity(point.opacity);
  350. if (point.scale < 1.) {
  351. p.translate(size, size);
  352. p.scale(point.scale, point.scale);
  353. p.translate(-size, -size);
  354. }
  355. const auto shift = (2 * size - (Emoji::GetSizeLarge() / ratio)) / 2;
  356. emoji->paint(p, {
  357. .textColor = gift.backdrop.patternColor,
  358. .position = QPoint(shift, shift),
  359. });
  360. }
  361. PreviewWrap::PreviewWrap(
  362. not_null<QWidget*> parent,
  363. not_null<PeerData*> recipient,
  364. rpl::producer<GiftDetails> details)
  365. : RpWidget(parent)
  366. , _history(recipient->owner().history(recipient->session().userPeerId()))
  367. , _recipient(recipient)
  368. , _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
  369. , _style(std::make_unique<ChatStyle>(
  370. _history->session().colorIndicesValue()))
  371. , _delegate(std::make_unique<PreviewDelegate>(
  372. parent,
  373. _style.get(),
  374. [=] { update(); }))
  375. , _position(0, st::msgMargin.bottom()) {
  376. _style->apply(_theme.get());
  377. using namespace HistoryView;
  378. _history->owner().viewRepaintRequest(
  379. ) | rpl::start_with_next([=](not_null<const Element*> view) {
  380. if (view == _item.get()) {
  381. update();
  382. }
  383. }, lifetime());
  384. _history->session().downloaderTaskFinished() | rpl::start_with_next([=] {
  385. update();
  386. }, lifetime());
  387. prepare(std::move(details));
  388. }
  389. void ShowSentToast(
  390. not_null<Window::SessionController*> window,
  391. const GiftDescriptor &descriptor,
  392. const GiftDetails &details) {
  393. const auto &st = st::historyPremiumToast;
  394. const auto skip = st.padding.top();
  395. const auto size = st.style.font->height * 2;
  396. const auto document = LookupGiftSticker(&window->session(), descriptor);
  397. const auto leftSkip = document
  398. ? (skip + size + skip - st.padding.left())
  399. : 0;
  400. auto text = v::match(descriptor, [&](const GiftTypePremium &gift) {
  401. return tr::lng_action_gift_premium_about(
  402. tr::now,
  403. Text::RichLangValue);
  404. }, [&](const GiftTypeStars &gift) {
  405. const auto amount = gift.info.stars
  406. + (details.upgraded ? gift.info.starsToUpgrade : 0);
  407. return tr::lng_gift_sent_about(
  408. tr::now,
  409. lt_count,
  410. amount,
  411. Text::RichLangValue);
  412. });
  413. const auto strong = window->showToast({
  414. .title = tr::lng_gift_sent_title(tr::now),
  415. .text = std::move(text),
  416. .padding = rpl::single(QMargins(leftSkip, 0, 0, 0)),
  417. .st = &st,
  418. .attach = RectPart::Top,
  419. .duration = kSentToastDuration,
  420. }).get();
  421. if (!strong || !document) {
  422. return;
  423. }
  424. const auto widget = strong->widget();
  425. const auto preview = CreateChild<RpWidget>(widget.get());
  426. preview->moveToLeft(skip, skip);
  427. preview->resize(size, size);
  428. preview->show();
  429. const auto bytes = document->createMediaView()->bytes();
  430. const auto filepath = document->filepath();
  431. const auto ratio = style::DevicePixelRatio();
  432. const auto player = preview->lifetime().make_state<Lottie::SinglePlayer>(
  433. Lottie::ReadContent(bytes, filepath),
  434. Lottie::FrameRequest{ QSize(size, size) * ratio },
  435. Lottie::Quality::Default);
  436. preview->paintRequest(
  437. ) | rpl::start_with_next([=] {
  438. if (!player->ready()) {
  439. return;
  440. }
  441. const auto image = player->frame();
  442. QPainter(preview).drawImage(
  443. QRect(QPoint(), image.size() / ratio),
  444. image);
  445. if (player->frameIndex() + 1 != player->framesCount()) {
  446. player->markFrameShown();
  447. }
  448. }, preview->lifetime());
  449. player->updates(
  450. ) | rpl::start_with_next([=] {
  451. preview->update();
  452. }, preview->lifetime());
  453. }
  454. PreviewWrap::~PreviewWrap() {
  455. _item = {};
  456. }
  457. void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
  458. std::move(details) | rpl::start_with_next([=](GiftDetails details) {
  459. const auto &descriptor = details.descriptor;
  460. const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
  461. const auto stars = (details.byStars && data.stars)
  462. ? data.stars
  463. : (data.currency == kCreditsCurrency)
  464. ? data.cost
  465. : 0;
  466. return stars
  467. ? tr::lng_gift_stars_title(tr::now, lt_count, stars)
  468. : FillAmountAndCurrency(data.cost, data.currency, true);
  469. }, [&](GiftTypeStars data) {
  470. const auto stars = data.info.stars
  471. + (details.upgraded ? data.info.starsToUpgrade : 0);
  472. return stars
  473. ? tr::lng_gift_stars_title(tr::now, lt_count, stars)
  474. : QString();
  475. });
  476. const auto name = _history->session().user()->shortName();
  477. const auto text = cost.isEmpty()
  478. ? tr::lng_action_gift_unique_received(tr::now, lt_user, name)
  479. : _recipient->isSelf()
  480. ? tr::lng_action_gift_self_bought(tr::now, lt_cost, cost)
  481. : _recipient->isBroadcast()
  482. ? tr::lng_action_gift_sent_channel(
  483. tr::now,
  484. lt_user,
  485. name,
  486. lt_name,
  487. _recipient->name(),
  488. lt_cost,
  489. cost)
  490. : tr::lng_action_gift_received(
  491. tr::now,
  492. lt_user,
  493. name,
  494. lt_cost,
  495. cost);
  496. const auto item = _history->makeMessage({
  497. .id = _history->nextNonHistoryEntryId(),
  498. .flags = (MessageFlag::FakeAboutView
  499. | MessageFlag::FakeHistoryItem
  500. | MessageFlag::Local),
  501. .from = _history->peer->id,
  502. }, PreparedServiceText{ { text } });
  503. auto owned = AdminLog::OwnedItem(_delegate.get(), item);
  504. owned->overrideMedia(std::make_unique<MediaGeneric>(
  505. owned.get(),
  506. GenerateGiftMedia(owned.get(), _item.get(), _recipient, details),
  507. MediaGenericDescriptor{
  508. .maxWidth = st::chatIntroWidth,
  509. .service = true,
  510. }));
  511. _item = std::move(owned);
  512. if (width() >= st::msgMinWidth) {
  513. resizeTo(width());
  514. }
  515. update();
  516. }, lifetime());
  517. widthValue(
  518. ) | rpl::filter([=](int width) {
  519. return width >= st::msgMinWidth;
  520. }) | rpl::start_with_next([=](int width) {
  521. resizeTo(width);
  522. }, lifetime());
  523. _history->owner().itemResizeRequest(
  524. ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
  525. if (_item && item == _item->data() && width() >= st::msgMinWidth) {
  526. resizeTo(width());
  527. }
  528. }, lifetime());
  529. }
  530. void PreviewWrap::resizeTo(int width) {
  531. const auto height = _position.y()
  532. + _item->resizeGetHeight(width)
  533. + _position.y()
  534. + st::msgServiceMargin.top()
  535. + st::msgServiceGiftBoxTopSkip
  536. - st::msgServiceMargin.bottom();
  537. resize(width, height);
  538. }
  539. void PreviewWrap::paintEvent(QPaintEvent *e) {
  540. auto p = Painter(this);
  541. const auto clip = e->rect();
  542. if (!clip.isEmpty()) {
  543. p.setClipRect(clip);
  544. Window::SectionWidget::PaintBackground(
  545. p,
  546. _theme.get(),
  547. QSize(width(), window()->height()),
  548. clip);
  549. }
  550. auto context = _theme->preparePaintContext(
  551. _style.get(),
  552. rect(),
  553. e->rect(),
  554. !window()->isActiveWindow());
  555. p.translate(_position);
  556. _item->draw(p, context);
  557. }
  558. [[nodiscard]] rpl::producer<PremiumGiftsDescriptor> GiftsPremium(
  559. not_null<Main::Session*> session,
  560. not_null<PeerData*> peer) {
  561. struct Session {
  562. PremiumGiftsDescriptor last;
  563. };
  564. static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
  565. return [=](auto consumer) {
  566. auto lifetime = rpl::lifetime();
  567. auto i = Map.find(session);
  568. if (i == end(Map)) {
  569. i = Map.emplace(session, Session()).first;
  570. session->lifetime().add([=] { Map.remove(session); });
  571. }
  572. if (!i->second.last.list.empty()) {
  573. consumer.put_next_copy(i->second.last);
  574. }
  575. using namespace Api;
  576. const auto api = std::make_shared<PremiumGiftCodeOptions>(peer);
  577. api->request() | rpl::start_with_error_done([=](QString error) {
  578. consumer.put_next({});
  579. }, [=] {
  580. const auto &options = api->optionsForPeer();
  581. auto list = std::vector<GiftTypePremium>();
  582. list.reserve(options.size());
  583. auto minMonthsGift = GiftTypePremium();
  584. for (const auto &option : options) {
  585. if (option.currency != kCreditsCurrency) {
  586. list.push_back({
  587. .cost = option.cost,
  588. .currency = option.currency,
  589. .months = option.months,
  590. });
  591. if (!minMonthsGift.months
  592. || option.months < minMonthsGift.months) {
  593. minMonthsGift = list.back();
  594. }
  595. }
  596. }
  597. for (const auto &option : options) {
  598. if (option.currency == kCreditsCurrency) {
  599. const auto i = ranges::find(
  600. list,
  601. option.months,
  602. &GiftTypePremium::months);
  603. if (i != end(list)) {
  604. i->stars = option.cost;
  605. }
  606. }
  607. }
  608. for (auto &gift : list) {
  609. if (gift.months > minMonthsGift.months
  610. && gift.currency == minMonthsGift.currency) {
  611. const auto costPerMonth = gift.cost / (1. * gift.months);
  612. const auto maxCostPerMonth = minMonthsGift.cost
  613. / (1. * minMonthsGift.months);
  614. const auto costRatio = costPerMonth / maxCostPerMonth;
  615. const auto discount = 1. - costRatio;
  616. const auto discountPercent = 100 * discount;
  617. const auto value = int(base::SafeRound(discountPercent));
  618. if (value > 0 && value < 100) {
  619. gift.discountPercent = value;
  620. }
  621. }
  622. }
  623. ranges::sort(list, ranges::less(), &GiftTypePremium::months);
  624. auto &map = Map[session];
  625. if (map.last.list != list) {
  626. map.last = PremiumGiftsDescriptor{
  627. std::move(list),
  628. api,
  629. };
  630. consumer.put_next_copy(map.last);
  631. }
  632. }, lifetime);
  633. return lifetime;
  634. };
  635. }
  636. [[nodiscard]] rpl::producer<std::vector<GiftTypeStars>> GiftsStars(
  637. not_null<Main::Session*> session,
  638. not_null<PeerData*> peer) {
  639. struct Session {
  640. std::vector<GiftTypeStars> last;
  641. };
  642. static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
  643. return [=](auto consumer) {
  644. auto lifetime = rpl::lifetime();
  645. auto i = Map.find(session);
  646. if (i == end(Map)) {
  647. i = Map.emplace(session, Session()).first;
  648. session->lifetime().add([=] { Map.remove(session); });
  649. }
  650. if (!i->second.last.empty()) {
  651. consumer.put_next_copy(i->second.last);
  652. }
  653. using namespace Api;
  654. const auto api = lifetime.make_state<PremiumGiftCodeOptions>(peer);
  655. api->requestStarGifts(
  656. ) | rpl::start_with_error_done([=](QString error) {
  657. consumer.put_next({});
  658. }, [=] {
  659. auto list = std::vector<GiftTypeStars>();
  660. const auto &gifts = api->starGifts();
  661. list.reserve(gifts.size());
  662. for (auto &gift : gifts) {
  663. list.push_back({ .info = gift });
  664. }
  665. ranges::stable_sort(list, [](const auto &a, const auto &b) {
  666. return a.info.soldOut < b.info.soldOut;
  667. });
  668. auto &map = Map[session];
  669. if (map.last != list) {
  670. map.last = list;
  671. consumer.put_next_copy(list);
  672. }
  673. }, lifetime);
  674. return lifetime;
  675. };
  676. }
  677. [[nodiscard]] Text::String TabTextForPrice(
  678. not_null<Main::Session*> session,
  679. int price) {
  680. const auto simple = [](const QString &text) {
  681. return Text::String(st::semiboldTextStyle, text);
  682. };
  683. if (price == kPriceTabAll) {
  684. return simple(tr::lng_gift_stars_tabs_all(tr::now));
  685. } else if (price == kPriceTabLimited) {
  686. return simple(tr::lng_gift_stars_tabs_limited(tr::now));
  687. } else if (price == kPriceTabInStock) {
  688. return simple(tr::lng_gift_stars_tabs_in_stock(tr::now));
  689. }
  690. auto &manager = session->data().customEmojiManager();
  691. auto result = Text::String();
  692. result.setMarkedText(
  693. st::semiboldTextStyle,
  694. manager.creditsEmoji().append(QString::number(price)),
  695. kMarkupTextOptions,
  696. Core::TextContext({ .session = session }));
  697. return result;
  698. }
  699. struct GiftPriceTabs {
  700. rpl::producer<int> priceTab;
  701. object_ptr<RpWidget> widget;
  702. };
  703. [[nodiscard]] GiftPriceTabs MakeGiftsPriceTabs(
  704. not_null<Window::SessionController*> window,
  705. not_null<PeerData*> peer,
  706. rpl::producer<std::vector<GiftTypeStars>> gifts) {
  707. auto widget = object_ptr<RpWidget>((QWidget*)nullptr);
  708. const auto raw = widget.data();
  709. struct Button {
  710. QRect geometry;
  711. Text::String text;
  712. int price = 0;
  713. bool active = false;
  714. };
  715. struct State {
  716. rpl::variable<std::vector<int>> prices;
  717. rpl::variable<int> priceTab = kPriceTabAll;
  718. rpl::variable<int> fullWidth;
  719. std::vector<Button> buttons;
  720. int dragx = 0;
  721. int pressx = 0;
  722. float64 dragscroll = 0.;
  723. float64 scroll = 0.;
  724. int scrollMax = 0;
  725. int selected = -1;
  726. int pressed = -1;
  727. int active = -1;
  728. };
  729. const auto state = raw->lifetime().make_state<State>();
  730. const auto scroll = [=] {
  731. return QPoint(int(base::SafeRound(state->scroll)), 0);
  732. };
  733. state->prices = std::move(
  734. gifts
  735. ) | rpl::map([](const std::vector<GiftTypeStars> &gifts) {
  736. auto result = std::vector<int>();
  737. result.push_back(kPriceTabAll);
  738. auto special = 1;
  739. auto same = true;
  740. auto sameKey = 0;
  741. auto hasNonSoldOut = false;
  742. auto hasSoldOut = false;
  743. auto hasLimited = false;
  744. for (const auto &gift : gifts) {
  745. if (same) {
  746. const auto key = gift.info.stars
  747. * (gift.info.limitedCount ? -1 : 1);
  748. if (!sameKey) {
  749. sameKey = key;
  750. } else if (sameKey != key) {
  751. same = false;
  752. }
  753. }
  754. if (IsSoldOut(gift.info)) {
  755. hasSoldOut = true;
  756. } else {
  757. hasNonSoldOut = true;
  758. }
  759. if (gift.info.limitedCount) {
  760. hasLimited = true;
  761. }
  762. if (!ranges::contains(result, gift.info.stars)) {
  763. result.push_back(gift.info.stars);
  764. }
  765. }
  766. if (same) {
  767. return std::vector<int>();
  768. }
  769. if (hasSoldOut && hasNonSoldOut) {
  770. result.insert(begin(result) + (special++), kPriceTabInStock);
  771. }
  772. if (hasLimited) {
  773. result.insert(begin(result) + (special++), kPriceTabLimited);
  774. }
  775. ranges::sort(begin(result) + 1, end(result));
  776. return result;
  777. });
  778. const auto setSelected = [=](int index) {
  779. const auto was = (state->selected >= 0);
  780. const auto now = (index >= 0);
  781. state->selected = index;
  782. if (was != now) {
  783. raw->setCursor(now ? style::cur_pointer : style::cur_default);
  784. }
  785. };
  786. const auto setActive = [=](int index) {
  787. const auto was = state->active;
  788. if (was == index) {
  789. return;
  790. }
  791. if (was >= 0 && was < state->buttons.size()) {
  792. state->buttons[was].active = false;
  793. }
  794. state->active = index;
  795. state->buttons[index].active = true;
  796. raw->update();
  797. state->priceTab = state->buttons[index].price;
  798. };
  799. const auto session = &peer->session();
  800. state->prices.value(
  801. ) | rpl::start_with_next([=](const std::vector<int> &prices) {
  802. auto x = st::giftBoxTabsMargin.left();
  803. auto y = st::giftBoxTabsMargin.top();
  804. setSelected(-1);
  805. state->buttons.resize(prices.size());
  806. const auto padding = st::giftBoxTabPadding;
  807. auto currentPrice = state->priceTab.current();
  808. if (!ranges::contains(prices, currentPrice)) {
  809. currentPrice = kPriceTabAll;
  810. }
  811. state->active = -1;
  812. for (auto i = 0, count = int(prices.size()); i != count; ++i) {
  813. const auto price = prices[i];
  814. auto &button = state->buttons[i];
  815. if (button.text.isEmpty() || button.price != price) {
  816. button.price = price;
  817. button.text = TabTextForPrice(session, price);
  818. }
  819. button.active = (price == currentPrice);
  820. if (button.active) {
  821. state->active = i;
  822. }
  823. const auto width = button.text.maxWidth();
  824. const auto height = st::giftBoxTabStyle.font->height;
  825. const auto r = QRect(0, 0, width, height).marginsAdded(padding);
  826. button.geometry = QRect(QPoint(x, y), r.size());
  827. x += r.width() + st::giftBoxTabSkip;
  828. }
  829. state->fullWidth = x
  830. - st::giftBoxTabSkip
  831. + st::giftBoxTabsMargin.right();
  832. const auto height = state->buttons.empty()
  833. ? 0
  834. : (y
  835. + state->buttons.back().geometry.height()
  836. + st::giftBoxTabsMargin.bottom());
  837. raw->resize(raw->width(), height);
  838. raw->update();
  839. }, raw->lifetime());
  840. rpl::combine(
  841. raw->widthValue(),
  842. state->fullWidth.value()
  843. ) | rpl::start_with_next([=](int outer, int inner) {
  844. state->scrollMax = std::max(0, inner - outer);
  845. }, raw->lifetime());
  846. raw->setMouseTracking(true);
  847. raw->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
  848. const auto type = e->type();
  849. switch (type) {
  850. case QEvent::Leave: setSelected(-1); break;
  851. case QEvent::MouseMove: {
  852. const auto me = static_cast<QMouseEvent*>(e.get());
  853. const auto mousex = me->pos().x();
  854. const auto drag = QApplication::startDragDistance();
  855. if (state->dragx > 0) {
  856. state->scroll = std::clamp(
  857. state->dragscroll + state->dragx - mousex,
  858. 0.,
  859. state->scrollMax * 1.);
  860. raw->update();
  861. break;
  862. } else if (state->pressx > 0
  863. && std::abs(state->pressx - mousex) > drag) {
  864. state->dragx = state->pressx;
  865. state->dragscroll = state->scroll;
  866. }
  867. const auto position = me->pos() + scroll();
  868. for (auto i = 0, c = int(state->buttons.size()); i != c; ++i) {
  869. if (state->buttons[i].geometry.contains(position)) {
  870. setSelected(i);
  871. break;
  872. }
  873. }
  874. } break;
  875. case QEvent::Wheel: {
  876. const auto me = static_cast<QWheelEvent*>(e.get());
  877. state->scroll = std::clamp(
  878. state->scroll - ScrollDeltaF(me).x(),
  879. 0.,
  880. state->scrollMax * 1.);
  881. raw->update();
  882. } break;
  883. case QEvent::MouseButtonPress: {
  884. const auto me = static_cast<QMouseEvent*>(e.get());
  885. if (me->button() != Qt::LeftButton) {
  886. break;
  887. }
  888. state->pressed = state->selected;
  889. state->pressx = me->pos().x();
  890. } break;
  891. case QEvent::MouseButtonRelease: {
  892. const auto me = static_cast<QMouseEvent*>(e.get());
  893. if (me->button() != Qt::LeftButton) {
  894. break;
  895. }
  896. const auto dragx = std::exchange(state->dragx, 0);
  897. const auto pressed = std::exchange(state->pressed, -1);
  898. state->pressx = 0;
  899. if (!dragx && pressed >= 0 && state->selected == pressed) {
  900. setActive(pressed);
  901. }
  902. } break;
  903. }
  904. }, raw->lifetime());
  905. raw->paintRequest() | rpl::start_with_next([=] {
  906. auto p = QPainter(raw);
  907. auto hq = PainterHighQualityEnabler(p);
  908. const auto padding = st::giftBoxTabPadding;
  909. const auto shift = -scroll();
  910. for (const auto &button : state->buttons) {
  911. const auto geometry = button.geometry.translated(shift);
  912. if (button.active) {
  913. p.setBrush(st::giftBoxTabBgActive);
  914. p.setPen(Qt::NoPen);
  915. const auto radius = geometry.height() / 2.;
  916. p.drawRoundedRect(geometry, radius, radius);
  917. p.setPen(st::giftBoxTabFgActive);
  918. } else {
  919. p.setPen(st::giftBoxTabFg);
  920. }
  921. button.text.draw(p, {
  922. .position = geometry.marginsRemoved(padding).topLeft(),
  923. .availableWidth = button.text.maxWidth(),
  924. });
  925. }
  926. {
  927. const auto &icon = st::defaultEmojiSuggestions;
  928. const auto w = icon.fadeRight.width();
  929. const auto &c = st::boxDividerBg->c;
  930. const auto r = QRect(0, 0, w, raw->height());
  931. const auto s = std::abs(float64(shift.x()));
  932. constexpr auto kF = 0.5;
  933. const auto opacityRight = (state->scrollMax - s)
  934. / (icon.fadeRight.width() * kF);
  935. p.setOpacity(std::clamp(std::abs(opacityRight), 0., 1.));
  936. icon.fadeRight.fill(p, r.translated(raw->width() - w, 0), c);
  937. const auto opacityLeft = s / (icon.fadeLeft.width() * kF);
  938. p.setOpacity(std::clamp(std::abs(opacityLeft), 0., 1.));
  939. icon.fadeLeft.fill(p, r, c);
  940. }
  941. }, raw->lifetime());
  942. return {
  943. .priceTab = state->priceTab.value(),
  944. .widget = std::move(widget),
  945. };
  946. }
  947. [[nodiscard]] int StarGiftMessageLimit(not_null<Main::Session*> session) {
  948. return session->appConfig().get<int>(
  949. u"stargifts_message_length_max"_q,
  950. 255);
  951. }
  952. [[nodiscard]] not_null<InputField*> AddPartInput(
  953. not_null<Window::SessionController*> controller,
  954. not_null<VerticalLayout*> container,
  955. not_null<QWidget*> outer,
  956. rpl::producer<QString> placeholder,
  957. QString current,
  958. int limit) {
  959. const auto field = container->add(
  960. object_ptr<InputField>(
  961. container,
  962. st::giftBoxTextField,
  963. InputField::Mode::NoNewlines,
  964. std::move(placeholder),
  965. current),
  966. st::giftBoxTextPadding);
  967. field->setMaxLength(limit);
  968. AddLengthLimitLabel(field, limit, std::nullopt, st::giftBoxLimitTop);
  969. const auto toggle = CreateChild<EmojiButton>(
  970. container,
  971. st::defaultComposeFiles.emoji);
  972. toggle->show();
  973. field->geometryValue() | rpl::start_with_next([=](QRect r) {
  974. toggle->move(
  975. r.x() + r.width() - toggle->width(),
  976. r.y() - st::giftBoxEmojiToggleTop);
  977. }, toggle->lifetime());
  978. using namespace ChatHelpers;
  979. const auto panel = field->lifetime().make_state<TabbedPanel>(
  980. outer,
  981. controller,
  982. object_ptr<TabbedSelector>(
  983. nullptr,
  984. controller->uiShow(),
  985. Window::GifPauseReason::Layer,
  986. TabbedSelector::Mode::EmojiOnly));
  987. panel->setDesiredHeightValues(
  988. 1.,
  989. st::emojiPanMinHeight / 2,
  990. st::emojiPanMinHeight);
  991. panel->hide();
  992. panel->selector()->setAllowEmojiWithoutPremium(true);
  993. panel->selector()->emojiChosen(
  994. ) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
  995. InsertEmojiAtCursor(field->textCursor(), data.emoji);
  996. }, field->lifetime());
  997. panel->selector()->customEmojiChosen(
  998. ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
  999. Data::InsertCustomEmoji(field, data.document);
  1000. }, field->lifetime());
  1001. const auto updateEmojiPanelGeometry = [=] {
  1002. const auto parent = panel->parentWidget();
  1003. const auto global = toggle->mapToGlobal({ 0, 0 });
  1004. const auto local = parent->mapFromGlobal(global);
  1005. panel->moveBottomRight(
  1006. local.y(),
  1007. local.x() + toggle->width() * 3);
  1008. };
  1009. const auto filterCallback = [=](not_null<QEvent*> event) {
  1010. const auto type = event->type();
  1011. if (type == QEvent::Move || type == QEvent::Resize) {
  1012. // updateEmojiPanelGeometry uses not only container geometry, but
  1013. // also container children geometries that will be updated later.
  1014. crl::on_main(field, updateEmojiPanelGeometry);
  1015. }
  1016. return base::EventFilterResult::Continue;
  1017. };
  1018. for (auto widget = (QWidget*)field, end = (QWidget*)outer->parentWidget()
  1019. ; widget && widget != end
  1020. ; widget = widget->parentWidget()) {
  1021. base::install_event_filter(field, widget, filterCallback);
  1022. }
  1023. toggle->installEventFilter(panel);
  1024. toggle->addClickHandler([=] {
  1025. panel->toggleAnimated();
  1026. });
  1027. return field;
  1028. }
  1029. void SendGift(
  1030. not_null<Window::SessionController*> window,
  1031. not_null<PeerData*> peer,
  1032. std::shared_ptr<Api::PremiumGiftCodeOptions> api,
  1033. const GiftDetails &details,
  1034. Fn<void(Payments::CheckoutResult)> done) {
  1035. const auto processNonPanelPaymentFormFactory
  1036. = Payments::ProcessNonPanelPaymentFormFactory(window, done);
  1037. v::match(details.descriptor, [&](const GiftTypePremium &gift) {
  1038. if (details.byStars && gift.stars) {
  1039. auto invoice = Payments::InvoicePremiumGiftCode{
  1040. .purpose = Payments::InvoicePremiumGiftCodeUsers{
  1041. .users = { peer->asUser() },
  1042. .message = details.text,
  1043. },
  1044. .currency = Ui::kCreditsCurrency,
  1045. .randomId = details.randomId,
  1046. .amount = uint64(gift.stars),
  1047. .storeQuantity = 1,
  1048. .users = 1,
  1049. .months = gift.months,
  1050. };
  1051. Payments::CheckoutProcess::Start(
  1052. std::move(invoice),
  1053. done,
  1054. processNonPanelPaymentFormFactory);
  1055. } else {
  1056. auto invoice = api->invoice(1, gift.months);
  1057. invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{
  1058. .users = { peer->asUser() },
  1059. .message = details.text,
  1060. };
  1061. Payments::CheckoutProcess::Start(std::move(invoice), done);
  1062. }
  1063. }, [&](const GiftTypeStars &gift) {
  1064. Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{
  1065. .giftId = gift.info.id,
  1066. .randomId = details.randomId,
  1067. .message = details.text,
  1068. .recipient = peer,
  1069. .limitedCount = gift.info.limitedCount,
  1070. .anonymous = details.anonymous,
  1071. .upgraded = details.upgraded,
  1072. }, done, processNonPanelPaymentFormFactory);
  1073. });
  1074. }
  1075. [[nodiscard]] std::shared_ptr<Data::UniqueGift> FindUniqueGift(
  1076. not_null<Main::Session*> session,
  1077. const MTPUpdates &updates) {
  1078. auto result = std::shared_ptr<Data::UniqueGift>();
  1079. const auto checkAction = [&](const MTPMessageAction &action) {
  1080. action.match([&](const MTPDmessageActionStarGiftUnique &data) {
  1081. if (const auto gift = Api::FromTL(session, data.vgift())) {
  1082. result = gift->unique;
  1083. }
  1084. }, [](const auto &) {});
  1085. };
  1086. updates.match([&](const MTPDupdates &data) {
  1087. for (const auto &update : data.vupdates().v) {
  1088. update.match([&](const MTPDupdateNewMessage &data) {
  1089. data.vmessage().match([&](const MTPDmessageService &data) {
  1090. checkAction(data.vaction());
  1091. }, [](const auto &) {});
  1092. }, [](const auto &) {});
  1093. }
  1094. }, [](const auto &) {});
  1095. return result;
  1096. }
  1097. void ShowGiftUpgradedToast(
  1098. base::weak_ptr<Window::SessionController> weak,
  1099. not_null<Main::Session*> session,
  1100. const MTPUpdates &result) {
  1101. const auto gift = FindUniqueGift(session, result);
  1102. if (const auto strong = gift ? weak.get() : nullptr) {
  1103. strong->showToast({
  1104. .title = tr::lng_gift_upgraded_title(tr::now),
  1105. .text = tr::lng_gift_upgraded_about(
  1106. tr::now,
  1107. lt_name,
  1108. Text::Bold(Data::UniqueGiftName(*gift)),
  1109. Ui::Text::WithEntities),
  1110. .duration = kUpgradeDoneToastDuration,
  1111. });
  1112. }
  1113. }
  1114. void SendStarsFormRequest(
  1115. not_null<Window::SessionController*> controller,
  1116. Settings::SmallBalanceResult result,
  1117. uint64 formId,
  1118. MTPInputInvoice invoice,
  1119. Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done) {
  1120. using BalanceResult = Settings::SmallBalanceResult;
  1121. const auto session = &controller->session();
  1122. if (result == BalanceResult::Success
  1123. || result == BalanceResult::Already) {
  1124. const auto weak = base::make_weak(controller);
  1125. session->api().request(MTPpayments_SendStarsForm(
  1126. MTP_long(formId),
  1127. invoice
  1128. )).done([=](const MTPpayments_PaymentResult &result) {
  1129. result.match([&](const MTPDpayments_paymentResult &data) {
  1130. session->api().applyUpdates(data.vupdates());
  1131. done(Payments::CheckoutResult::Paid, &data.vupdates());
  1132. }, [&](const MTPDpayments_paymentVerificationNeeded &data) {
  1133. done(Payments::CheckoutResult::Failed, nullptr);
  1134. });
  1135. }).fail([=](const MTP::Error &error) {
  1136. if (const auto strong = weak.get()) {
  1137. strong->showToast(error.type());
  1138. }
  1139. done(Payments::CheckoutResult::Failed, nullptr);
  1140. }).send();
  1141. } else if (result == BalanceResult::Cancelled) {
  1142. done(Payments::CheckoutResult::Cancelled, nullptr);
  1143. } else {
  1144. done(Payments::CheckoutResult::Failed, nullptr);
  1145. }
  1146. }
  1147. void UpgradeGift(
  1148. not_null<Window::SessionController*> window,
  1149. Data::SavedStarGiftId savedId,
  1150. bool keepDetails,
  1151. int stars,
  1152. Fn<void(Payments::CheckoutResult)> done) {
  1153. const auto session = &window->session();
  1154. const auto weak = base::make_weak(window);
  1155. auto formDone = [=](
  1156. Payments::CheckoutResult result,
  1157. const MTPUpdates *updates) {
  1158. if (result == Payments::CheckoutResult::Paid && updates) {
  1159. if (const auto strong = weak.get()) {
  1160. ShowGiftUpgradedToast(strong, session, *updates);
  1161. }
  1162. }
  1163. done(result);
  1164. };
  1165. if (stars <= 0) {
  1166. using Flag = MTPpayments_UpgradeStarGift::Flag;
  1167. session->api().request(MTPpayments_UpgradeStarGift(
  1168. MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
  1169. Api::InputSavedStarGiftId(savedId)
  1170. )).done([=](const MTPUpdates &result) {
  1171. session->api().applyUpdates(result);
  1172. formDone(Payments::CheckoutResult::Paid, &result);
  1173. }).fail([=](const MTP::Error &error) {
  1174. if (const auto strong = weak.get()) {
  1175. strong->showToast(error.type());
  1176. }
  1177. formDone(Payments::CheckoutResult::Failed, nullptr);
  1178. }).send();
  1179. return;
  1180. }
  1181. using Flag = MTPDinputInvoiceStarGiftUpgrade::Flag;
  1182. RequestStarsFormAndSubmit(
  1183. window,
  1184. MTP_inputInvoiceStarGiftUpgrade(
  1185. MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
  1186. Api::InputSavedStarGiftId(savedId)),
  1187. std::move(formDone));
  1188. }
  1189. void SoldOutBox(
  1190. not_null<GenericBox*> box,
  1191. not_null<Window::SessionController*> window,
  1192. const GiftTypeStars &gift) {
  1193. Settings::ReceiptCreditsBox(
  1194. box,
  1195. window,
  1196. Data::CreditsHistoryEntry{
  1197. .firstSaleDate = base::unixtime::parse(gift.info.firstSaleDate),
  1198. .lastSaleDate = base::unixtime::parse(gift.info.lastSaleDate),
  1199. .credits = StarsAmount(gift.info.stars),
  1200. .bareGiftStickerId = gift.info.document->id,
  1201. .peerType = Data::CreditsHistoryEntry::PeerType::Peer,
  1202. .limitedCount = gift.info.limitedCount,
  1203. .limitedLeft = gift.info.limitedLeft,
  1204. .soldOutInfo = true,
  1205. .gift = true,
  1206. },
  1207. Data::SubscriptionEntry());
  1208. }
  1209. void AddUpgradeButton(
  1210. not_null<Ui::VerticalLayout*> container,
  1211. not_null<Main::Session*> session,
  1212. int cost,
  1213. not_null<PeerData*> peer,
  1214. Fn<void(bool)> toggled,
  1215. Fn<void()> preview) {
  1216. const auto button = container->add(
  1217. object_ptr<SettingsButton>(
  1218. container,
  1219. rpl::single(QString()),
  1220. st::settingsButtonNoIcon));
  1221. button->toggleOn(rpl::single(false))->toggledValue(
  1222. ) | rpl::start_with_next(toggled, button->lifetime());
  1223. auto star = session->data().customEmojiManager().creditsEmoji();
  1224. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  1225. button,
  1226. tr::lng_gift_send_unique(
  1227. lt_price,
  1228. rpl::single(star.append(' '
  1229. + Lang::FormatStarsAmountDecimal(
  1230. StarsAmount{ cost }))),
  1231. Text::WithEntities),
  1232. st::boxLabel,
  1233. st::defaultPopupMenu,
  1234. Core::TextContext({ .session = session }));
  1235. label->show();
  1236. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  1237. button->widthValue() | rpl::start_with_next([=](int outer) {
  1238. const auto padding = st::settingsButtonNoIcon.padding;
  1239. const auto inner = outer
  1240. - padding.left()
  1241. - padding.right()
  1242. - st::settingsButtonNoIcon.toggleSkip
  1243. - 2 * st::settingsButtonNoIcon.toggle.border
  1244. - 2 * st::settingsButtonNoIcon.toggle.diameter
  1245. - 2 * st::settingsButtonNoIcon.toggle.width;
  1246. label->resizeToWidth(inner);
  1247. label->moveToLeft(padding.left(), padding.top(), outer);
  1248. }, label->lifetime());
  1249. AddSkip(container);
  1250. const auto about = AddDividerText(
  1251. container,
  1252. (peer->isBroadcast()
  1253. ? tr::lng_gift_send_unique_about_channel(
  1254. lt_name,
  1255. rpl::single(TextWithEntities{ peer->name() }),
  1256. lt_link,
  1257. tr::lng_gift_send_unique_link() | Text::ToLink(),
  1258. Text::WithEntities)
  1259. : tr::lng_gift_send_unique_about(
  1260. lt_user,
  1261. rpl::single(TextWithEntities{ peer->shortName() }),
  1262. lt_link,
  1263. tr::lng_gift_send_unique_link() | Text::ToLink(),
  1264. Text::WithEntities)));
  1265. about->setClickHandlerFilter([=](const auto &...) {
  1266. preview();
  1267. return false;
  1268. });
  1269. }
  1270. void AddSoldLeftSlider(
  1271. not_null<RoundButton*> button,
  1272. const GiftTypeStars &gift) {
  1273. const auto still = gift.info.limitedLeft;
  1274. const auto total = gift.info.limitedCount;
  1275. const auto slider = CreateChild<RpWidget>(button->parentWidget());
  1276. struct State {
  1277. Text::String still;
  1278. Text::String sold;
  1279. int height = 0;
  1280. };
  1281. const auto state = slider->lifetime().make_state<State>();
  1282. const auto sold = total - still;
  1283. state->still.setText(
  1284. st::semiboldTextStyle,
  1285. tr::lng_gift_send_limited_left(tr::now, lt_count_decimal, still));
  1286. state->sold.setText(
  1287. st::semiboldTextStyle,
  1288. tr::lng_gift_send_limited_sold(tr::now, lt_count_decimal, sold));
  1289. state->height = st::giftLimitedPadding.top()
  1290. + st::semiboldFont->height
  1291. + st::giftLimitedPadding.bottom();
  1292. button->geometryValue() | rpl::start_with_next([=](QRect geometry) {
  1293. const auto space = st::giftLimitedBox.buttonPadding.top();
  1294. const auto skip = (space - state->height) / 2;
  1295. slider->setGeometry(
  1296. geometry.x(),
  1297. geometry.y() - skip - state->height,
  1298. geometry.width(),
  1299. state->height);
  1300. }, slider->lifetime());
  1301. slider->paintRequest() | rpl::start_with_next([=] {
  1302. const auto &padding = st::giftLimitedPadding;
  1303. const auto left = (padding.left() * 2) + state->still.maxWidth();
  1304. const auto right = (padding.right() * 2) + state->sold.maxWidth();
  1305. const auto space = slider->width() - left - right;
  1306. if (space <= 0) {
  1307. return;
  1308. }
  1309. const auto edge = left + ((space * still) / total);
  1310. const auto radius = st::buttonRadius;
  1311. auto p = QPainter(slider);
  1312. auto hq = PainterHighQualityEnabler(p);
  1313. p.setPen(Qt::NoPen);
  1314. p.setBrush(st::windowBgOver);
  1315. p.drawRoundedRect(
  1316. edge - (radius * 3),
  1317. 0,
  1318. slider->width() - (edge - (radius * 3)),
  1319. state->height,
  1320. radius,
  1321. radius);
  1322. p.setBrush(st::windowBgActive);
  1323. p.drawRoundedRect(0, 0, edge, state->height, radius, radius);
  1324. p.setPen(st::windowFgActive);
  1325. state->still.draw(p, {
  1326. .position = { padding.left(), padding.top() },
  1327. .availableWidth = left,
  1328. });
  1329. p.setPen(st::windowSubTextFg);
  1330. state->sold.draw(p, {
  1331. .position = { left + space + padding.right(), padding.top() },
  1332. .availableWidth = right,
  1333. });
  1334. }, slider->lifetime());
  1335. }
  1336. void SendGiftBox(
  1337. not_null<GenericBox*> box,
  1338. not_null<Window::SessionController*> window,
  1339. not_null<PeerData*> peer,
  1340. std::shared_ptr<Api::PremiumGiftCodeOptions> api,
  1341. const GiftDescriptor &descriptor) {
  1342. const auto stars = std::get_if<GiftTypeStars>(&descriptor);
  1343. const auto limited = stars
  1344. && (stars->info.limitedCount > stars->info.limitedLeft)
  1345. && (stars->info.limitedLeft > 0);
  1346. box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
  1347. box->setWidth(st::boxWideWidth);
  1348. box->setTitle(tr::lng_gift_send_title());
  1349. box->addTopButton(st::boxTitleClose, [=] {
  1350. box->closeBox();
  1351. });
  1352. const auto session = &window->session();
  1353. struct State {
  1354. rpl::variable<GiftDetails> details;
  1355. rpl::variable<bool> messageAllowed;
  1356. std::shared_ptr<Data::DocumentMedia> media;
  1357. bool submitting = false;
  1358. };
  1359. const auto state = box->lifetime().make_state<State>();
  1360. state->details = GiftDetails{
  1361. .descriptor = descriptor,
  1362. .randomId = base::RandomValue<uint64>(),
  1363. };
  1364. peer->updateFull();
  1365. state->messageAllowed = peer->session().changes().peerFlagsValue(
  1366. peer,
  1367. Data::PeerUpdate::Flag::StarsPerMessage
  1368. ) | rpl::map([=] {
  1369. return peer->starsPerMessageChecked() == 0;
  1370. });
  1371. auto cost = state->details.value(
  1372. ) | rpl::map([session](const GiftDetails &details) {
  1373. return v::match(details.descriptor, [&](const GiftTypePremium &data) {
  1374. const auto stars = (details.byStars && data.stars)
  1375. ? data.stars
  1376. : (data.currency == kCreditsCurrency)
  1377. ? data.cost
  1378. : 0;
  1379. if (stars) {
  1380. return CreditsEmojiSmall(session).append(
  1381. Lang::FormatCountDecimal(std::abs(stars)));
  1382. }
  1383. return TextWithEntities{
  1384. FillAmountAndCurrency(data.cost, data.currency),
  1385. };
  1386. }, [&](const GiftTypeStars &data) {
  1387. const auto amount = std::abs(data.info.stars)
  1388. + (details.upgraded ? data.info.starsToUpgrade : 0);
  1389. return CreditsEmojiSmall(session).append(
  1390. Lang::FormatCountDecimal(amount));
  1391. });
  1392. });
  1393. const auto document = LookupGiftSticker(session, descriptor);
  1394. if ((state->media = document ? document->createMediaView() : nullptr)) {
  1395. state->media->checkStickerLarge();
  1396. }
  1397. const auto container = box->verticalLayout();
  1398. container->add(object_ptr<PreviewWrap>(
  1399. container,
  1400. peer,
  1401. state->details.value()));
  1402. const auto messageWrap = container->add(
  1403. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  1404. container,
  1405. object_ptr<Ui::VerticalLayout>(container)));
  1406. messageWrap->toggleOn(state->messageAllowed.value());
  1407. messageWrap->finishAnimating();
  1408. const auto messageInner = messageWrap->entity();
  1409. const auto limit = StarGiftMessageLimit(session);
  1410. const auto text = AddPartInput(
  1411. window,
  1412. messageInner,
  1413. box->getDelegate()->outerContainer(),
  1414. tr::lng_gift_send_message(),
  1415. QString(),
  1416. limit);
  1417. text->changes() | rpl::start_with_next([=] {
  1418. auto now = state->details.current();
  1419. auto textWithTags = text->getTextWithAppliedMarkdown();
  1420. now.text = TextWithEntities{
  1421. std::move(textWithTags.text),
  1422. TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
  1423. };
  1424. state->details = std::move(now);
  1425. }, text->lifetime());
  1426. box->setFocusCallback([=] {
  1427. text->setFocusFast();
  1428. });
  1429. const auto allow = [=](not_null<DocumentData*> emoji) {
  1430. return true;
  1431. };
  1432. InitMessageFieldHandlers({
  1433. .session = session,
  1434. .show = window->uiShow(),
  1435. .field = text,
  1436. .customEmojiPaused = [=] {
  1437. using namespace Window;
  1438. return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
  1439. },
  1440. .allowPremiumEmoji = allow,
  1441. .allowMarkdownTags = {
  1442. InputField::kTagBold,
  1443. InputField::kTagItalic,
  1444. InputField::kTagUnderline,
  1445. InputField::kTagStrikeOut,
  1446. InputField::kTagSpoiler,
  1447. }
  1448. });
  1449. Emoji::SuggestionsController::Init(
  1450. box->getDelegate()->outerContainer(),
  1451. text,
  1452. session,
  1453. { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
  1454. if (stars) {
  1455. const auto cost = stars->info.starsToUpgrade;
  1456. if (cost > 0 && !peer->isSelf()) {
  1457. const auto id = stars->info.id;
  1458. const auto showing = std::make_shared<bool>();
  1459. AddDivider(container);
  1460. AddSkip(container);
  1461. AddUpgradeButton(container, session, cost, peer, [=](bool on) {
  1462. auto now = state->details.current();
  1463. now.upgraded = on;
  1464. state->details = std::move(now);
  1465. }, [=] {
  1466. if (*showing) {
  1467. return;
  1468. }
  1469. *showing = true;
  1470. ShowStarGiftUpgradeBox({
  1471. .controller = window,
  1472. .stargiftId = id,
  1473. .ready = [=](bool) { *showing = false; },
  1474. .peer = peer,
  1475. .cost = int(cost),
  1476. });
  1477. });
  1478. } else {
  1479. AddDivider(container);
  1480. }
  1481. AddSkip(container);
  1482. container->add(
  1483. object_ptr<SettingsButton>(
  1484. container,
  1485. tr::lng_gift_send_anonymous(),
  1486. st::settingsButtonNoIcon)
  1487. )->toggleOn(rpl::single(peer->isSelf()))->toggledValue(
  1488. ) | rpl::start_with_next([=](bool toggled) {
  1489. auto now = state->details.current();
  1490. now.anonymous = toggled;
  1491. state->details = std::move(now);
  1492. }, container->lifetime());
  1493. AddSkip(container);
  1494. }
  1495. v::match(descriptor, [&](const GiftTypePremium &data) {
  1496. AddDividerText(messageInner, tr::lng_gift_send_premium_about(
  1497. lt_user,
  1498. rpl::single(peer->shortName())));
  1499. if (const auto byStars = data.stars) {
  1500. const auto star = Ui::Text::IconEmoji(&st::starIconEmojiColored);
  1501. AddSkip(container);
  1502. container->add(
  1503. object_ptr<SettingsButton>(
  1504. container,
  1505. tr::lng_gift_send_pay_with_stars(
  1506. lt_amount,
  1507. rpl::single(base::duplicate(star).append(Lang::FormatCountDecimal(byStars))),
  1508. Ui::Text::WithEntities),
  1509. st::settingsButtonNoIcon)
  1510. )->toggleOn(rpl::single(false))->toggledValue(
  1511. ) | rpl::start_with_next([=](bool toggled) {
  1512. auto now = state->details.current();
  1513. now.byStars = toggled;
  1514. state->details = std::move(now);
  1515. }, container->lifetime());
  1516. AddSkip(container);
  1517. const auto balance = AddDividerText(
  1518. container,
  1519. tr::lng_gift_send_stars_balance(
  1520. lt_amount,
  1521. peer->session().credits().balanceValue(
  1522. ) | rpl::map([=](StarsAmount amount) {
  1523. return base::duplicate(star).append(
  1524. Lang::FormatStarsAmountDecimal(amount));
  1525. }),
  1526. lt_link,
  1527. tr::lng_gift_send_stars_balance_link(
  1528. ) | Ui::Text::ToLink(),
  1529. Ui::Text::WithEntities));
  1530. struct State {
  1531. Settings::BuyStarsHandler buyStars;
  1532. rpl::variable<bool> loading;
  1533. };
  1534. const auto state = balance->lifetime().make_state<State>();
  1535. state->loading = state->buyStars.loadingValue();
  1536. balance->setClickHandlerFilter([=](const auto &...) {
  1537. if (!state->loading.current()) {
  1538. state->buyStars.handler(window->uiShow())();
  1539. }
  1540. return false;
  1541. });
  1542. }
  1543. }, [&](const GiftTypeStars &) {
  1544. AddDividerText(container, peer->isSelf()
  1545. ? tr::lng_gift_send_anonymous_self()
  1546. : peer->isBroadcast()
  1547. ? tr::lng_gift_send_anonymous_about_channel()
  1548. : rpl::conditional(
  1549. state->messageAllowed.value(),
  1550. tr::lng_gift_send_anonymous_about(
  1551. lt_user,
  1552. rpl::single(peer->shortName()),
  1553. lt_recipient,
  1554. rpl::single(peer->shortName())),
  1555. tr::lng_gift_send_anonymous_about_paid(
  1556. lt_user,
  1557. rpl::single(peer->shortName()),
  1558. lt_recipient,
  1559. rpl::single(peer->shortName()))));
  1560. });
  1561. const auto buttonWidth = st::boxWideWidth
  1562. - st::giftBox.buttonPadding.left()
  1563. - st::giftBox.buttonPadding.right();
  1564. const auto button = box->addButton(rpl::single(QString()), [=] {
  1565. if (state->submitting) {
  1566. return;
  1567. }
  1568. state->submitting = true;
  1569. auto details = state->details.current();
  1570. if (!state->messageAllowed.current()) {
  1571. details.text = {};
  1572. }
  1573. const auto weak = MakeWeak(box);
  1574. const auto done = [=](Payments::CheckoutResult result) {
  1575. if (result == Payments::CheckoutResult::Paid) {
  1576. if (details.byStars
  1577. || v::is<GiftTypeStars>(details.descriptor)) {
  1578. window->session().credits().load(true);
  1579. }
  1580. const auto copy = state->media;
  1581. window->showPeerHistory(peer);
  1582. ShowSentToast(window, details.descriptor, details);
  1583. }
  1584. if (const auto strong = weak.data()) {
  1585. box->closeBox();
  1586. }
  1587. };
  1588. SendGift(window, peer, api, details, done);
  1589. });
  1590. if (limited) {
  1591. AddSoldLeftSlider(button, *stars);
  1592. }
  1593. SetButtonMarkedLabel(
  1594. button,
  1595. (peer->isSelf()
  1596. ? tr::lng_gift_send_button_self
  1597. : tr::lng_gift_send_button)(
  1598. lt_cost,
  1599. std::move(cost),
  1600. Text::WithEntities),
  1601. session,
  1602. st::creditsBoxButtonLabel,
  1603. &st::giftBox.button.textFg);
  1604. button->resizeToWidth(buttonWidth);
  1605. button->widthValue() | rpl::start_with_next([=](int width) {
  1606. if (width != buttonWidth) {
  1607. button->resizeToWidth(buttonWidth);
  1608. }
  1609. }, button->lifetime());
  1610. }
  1611. [[nodiscard]] object_ptr<RpWidget> MakeGiftsList(
  1612. not_null<Window::SessionController*> window,
  1613. not_null<PeerData*> peer,
  1614. rpl::producer<GiftsDescriptor> gifts) {
  1615. auto result = object_ptr<RpWidget>((QWidget*)nullptr);
  1616. const auto raw = result.data();
  1617. struct State {
  1618. Delegate delegate;
  1619. std::vector<std::unique_ptr<GiftButton>> buttons;
  1620. bool sending = false;
  1621. };
  1622. const auto state = raw->lifetime().make_state<State>(State{
  1623. .delegate = Delegate(&window->session(), GiftButtonMode::Full),
  1624. });
  1625. const auto single = state->delegate.buttonSize();
  1626. const auto shadow = st::defaultDropdownMenu.wrap.shadow;
  1627. const auto extend = shadow.extend;
  1628. auto &packs = window->session().giftBoxStickersPacks();
  1629. packs.updated() | rpl::start_with_next([=] {
  1630. for (const auto &button : state->buttons) {
  1631. button->update();
  1632. }
  1633. }, raw->lifetime());
  1634. std::move(
  1635. gifts
  1636. ) | rpl::start_with_next([=](const GiftsDescriptor &gifts) {
  1637. const auto width = st::boxWideWidth;
  1638. const auto padding = st::giftBoxPadding;
  1639. const auto available = width - padding.left() - padding.right();
  1640. const auto perRow = available / single.width();
  1641. const auto count = int(gifts.list.size());
  1642. auto order = ranges::views::ints
  1643. | ranges::views::take(count)
  1644. | ranges::to_vector;
  1645. if (SortForBirthday(peer)) {
  1646. ranges::stable_partition(order, [&](int i) {
  1647. const auto &gift = gifts.list[i];
  1648. const auto stars = std::get_if<GiftTypeStars>(&gift);
  1649. return stars && stars->info.birthday;
  1650. });
  1651. }
  1652. auto x = padding.left();
  1653. auto y = padding.top();
  1654. state->buttons.resize(count);
  1655. for (auto &button : state->buttons) {
  1656. if (!button) {
  1657. button = std::make_unique<GiftButton>(raw, &state->delegate);
  1658. button->show();
  1659. }
  1660. }
  1661. const auto api = gifts.api;
  1662. for (auto i = 0; i != count; ++i) {
  1663. const auto button = state->buttons[i].get();
  1664. const auto &descriptor = gifts.list[order[i]];
  1665. button->setDescriptor(descriptor, GiftButton::Mode::Full);
  1666. const auto last = !((i + 1) % perRow);
  1667. if (last) {
  1668. x = padding.left() + available - single.width();
  1669. }
  1670. button->setGeometry(QRect(QPoint(x, y), single), extend);
  1671. if (last) {
  1672. x = padding.left();
  1673. y += single.height() + st::giftBoxGiftSkip.y();
  1674. } else {
  1675. x += single.width() + st::giftBoxGiftSkip.x();
  1676. }
  1677. button->setClickedCallback([=] {
  1678. const auto star = std::get_if<GiftTypeStars>(&descriptor);
  1679. if (star && IsSoldOut(star->info)) {
  1680. window->show(Box(SoldOutBox, window, *star));
  1681. } else {
  1682. window->show(
  1683. Box(SendGiftBox, window, peer, api, descriptor));
  1684. }
  1685. });
  1686. }
  1687. if (count % perRow) {
  1688. y += padding.bottom() + single.height();
  1689. } else {
  1690. y += padding.bottom() - st::giftBoxGiftSkip.y();
  1691. }
  1692. raw->resize(raw->width(), count ? y : 0);
  1693. }, raw->lifetime());
  1694. return result;
  1695. }
  1696. void FillBg(not_null<RpWidget*> box) {
  1697. box->paintRequest() | rpl::start_with_next([=] {
  1698. auto p = QPainter(box);
  1699. auto hq = PainterHighQualityEnabler(p);
  1700. const auto radius = st::boxRadius;
  1701. p.setPen(Qt::NoPen);
  1702. p.setBrush(st::boxDividerBg);
  1703. p.drawRoundedRect(
  1704. box->rect().marginsAdded({ 0, 0, 0, 2 * radius }),
  1705. radius,
  1706. radius);
  1707. }, box->lifetime());
  1708. }
  1709. struct AddBlockArgs {
  1710. rpl::producer<QString> subtitle;
  1711. rpl::producer<TextWithEntities> about;
  1712. Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)> aboutFilter;
  1713. object_ptr<RpWidget> content;
  1714. };
  1715. void AddBlock(
  1716. not_null<VerticalLayout*> content,
  1717. not_null<Window::SessionController*> window,
  1718. AddBlockArgs &&args) {
  1719. content->add(
  1720. object_ptr<FlatLabel>(
  1721. content,
  1722. std::move(args.subtitle),
  1723. st::giftBoxSubtitle),
  1724. st::giftBoxSubtitleMargin);
  1725. const auto about = content->add(
  1726. object_ptr<FlatLabel>(
  1727. content,
  1728. std::move(args.about),
  1729. st::giftBoxAbout),
  1730. st::giftBoxAboutMargin);
  1731. about->setClickHandlerFilter(std::move(args.aboutFilter));
  1732. content->add(std::move(args.content));
  1733. }
  1734. [[nodiscard]] object_ptr<RpWidget> MakePremiumGifts(
  1735. not_null<Window::SessionController*> window,
  1736. not_null<PeerData*> peer) {
  1737. struct State {
  1738. rpl::variable<PremiumGiftsDescriptor> gifts;
  1739. };
  1740. auto state = std::make_unique<State>();
  1741. state->gifts = GiftsPremium(&window->session(), peer);
  1742. auto result = MakeGiftsList(window, peer, state->gifts.value(
  1743. ) | rpl::map([=](const PremiumGiftsDescriptor &gifts) {
  1744. return GiftsDescriptor{
  1745. gifts.list | ranges::to<std::vector<GiftDescriptor>>,
  1746. gifts.api,
  1747. };
  1748. }));
  1749. result->lifetime().add([state = std::move(state)] {});
  1750. return result;
  1751. }
  1752. [[nodiscard]] object_ptr<RpWidget> MakeStarsGifts(
  1753. not_null<Window::SessionController*> window,
  1754. not_null<PeerData*> peer) {
  1755. auto result = object_ptr<VerticalLayout>((QWidget*)nullptr);
  1756. struct State {
  1757. rpl::variable<std::vector<GiftTypeStars>> gifts;
  1758. rpl::variable<int> priceTab = kPriceTabAll;
  1759. };
  1760. const auto state = result->lifetime().make_state<State>();
  1761. state->gifts = GiftsStars(&window->session(), peer);
  1762. auto tabs = MakeGiftsPriceTabs(window, peer, state->gifts.value());
  1763. state->priceTab = std::move(tabs.priceTab);
  1764. result->add(std::move(tabs.widget));
  1765. result->add(MakeGiftsList(window, peer, rpl::combine(
  1766. state->gifts.value(),
  1767. state->priceTab.value()
  1768. ) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) {
  1769. gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) {
  1770. return (price == kPriceTabLimited)
  1771. ? (!gift.info.limitedCount)
  1772. : (price == kPriceTabInStock)
  1773. ? IsSoldOut(gift.info)
  1774. : (price && gift.info.stars != price);
  1775. }), end(gifts));
  1776. return GiftsDescriptor{
  1777. gifts | ranges::to<std::vector<GiftDescriptor>>(),
  1778. };
  1779. })));
  1780. return result;
  1781. }
  1782. void GiftBox(
  1783. not_null<GenericBox*> box,
  1784. not_null<Window::SessionController*> window,
  1785. not_null<PeerData*> peer) {
  1786. box->setWidth(st::boxWideWidth);
  1787. box->setStyle(st::creditsGiftBox);
  1788. box->setNoContentMargin(true);
  1789. box->setCustomCornersFilling(RectPart::FullTop);
  1790. box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
  1791. window->session().credits().load();
  1792. FillBg(box);
  1793. const auto &stUser = st::premiumGiftsUserpicButton;
  1794. const auto content = box->verticalLayout();
  1795. AddSkip(content, st::defaultVerticalListSkip * 5);
  1796. content->add(
  1797. object_ptr<CenterWrap<>>(
  1798. content,
  1799. object_ptr<UserpicButton>(content, peer, stUser))
  1800. )->setAttribute(Qt::WA_TransparentForMouseEvents);
  1801. AddSkip(content);
  1802. AddSkip(content);
  1803. Settings::AddMiniStars(
  1804. content,
  1805. CreateChild<RpWidget>(content),
  1806. stUser.photoSize,
  1807. box->width(),
  1808. 2.);
  1809. AddSkip(content);
  1810. AddSkip(box->verticalLayout());
  1811. const auto starsClickHandlerFilter = [=](const auto &...) {
  1812. window->showSettings(Settings::CreditsId());
  1813. return false;
  1814. };
  1815. if (peer->isUser() && !peer->isSelf()) {
  1816. const auto premiumClickHandlerFilter = [=](const auto &...) {
  1817. Settings::ShowPremium(window, u"gift_send"_q);
  1818. return false;
  1819. };
  1820. AddBlock(content, window, {
  1821. .subtitle = tr::lng_gift_premium_subtitle(),
  1822. .about = tr::lng_gift_premium_about(
  1823. lt_name,
  1824. rpl::single(Text::Bold(peer->shortName())),
  1825. lt_features,
  1826. tr::lng_gift_premium_features() | Text::ToLink(),
  1827. Text::WithEntities),
  1828. .aboutFilter = premiumClickHandlerFilter,
  1829. .content = MakePremiumGifts(window, peer),
  1830. });
  1831. }
  1832. AddBlock(content, window, {
  1833. .subtitle = (peer->isSelf()
  1834. ? tr::lng_gift_self_title()
  1835. : peer->isBroadcast()
  1836. ? tr::lng_gift_channel_title()
  1837. : tr::lng_gift_stars_subtitle()),
  1838. .about = (peer->isSelf()
  1839. ? tr::lng_gift_self_about(Text::WithEntities)
  1840. : peer->isBroadcast()
  1841. ? tr::lng_gift_channel_about(
  1842. lt_name,
  1843. rpl::single(Text::Bold(peer->name())),
  1844. Text::WithEntities)
  1845. : tr::lng_gift_stars_about(
  1846. lt_name,
  1847. rpl::single(Text::Bold(peer->shortName())),
  1848. lt_link,
  1849. tr::lng_gift_stars_link() | Text::ToLink(),
  1850. Text::WithEntities)),
  1851. .aboutFilter = starsClickHandlerFilter,
  1852. .content = MakeStarsGifts(window, peer),
  1853. });
  1854. }
  1855. struct SelfOption {
  1856. object_ptr<Ui::RpWidget> content = { nullptr };
  1857. Fn<bool(int, int, int)> overrideKey;
  1858. Fn<void()> activate;
  1859. };
  1860. class Controller final : public ContactsBoxController {
  1861. public:
  1862. Controller(
  1863. not_null<Main::Session*> session,
  1864. Fn<void(not_null<PeerData*>)> choose);
  1865. void noSearchSubmit();
  1866. bool overrideKeyboardNavigation(
  1867. int direction,
  1868. int fromIndex,
  1869. int toIndex) override;
  1870. private:
  1871. std::unique_ptr<PeerListRow> createRow(
  1872. not_null<UserData*> user) override;
  1873. void prepareViewHook() override;
  1874. void rowClicked(not_null<PeerListRow*> row) override;
  1875. const Fn<void(not_null<PeerData*>)> _choose;
  1876. SelfOption _selfOption;
  1877. };
  1878. [[nodiscard]] SelfOption MakeSelfOption(
  1879. not_null<Main::Session*> session,
  1880. Fn<void()> activate) {
  1881. class SelfController final : public PeerListController {
  1882. public:
  1883. SelfController(
  1884. not_null<Main::Session*> session,
  1885. Fn<void()> activate)
  1886. : _session(session)
  1887. , _activate(std::move(activate)) {
  1888. }
  1889. void prepare() override {
  1890. auto row = std::make_unique<PeerListRow>(_session->user());
  1891. row->setCustomStatus(tr::lng_gift_self_status(tr::now));
  1892. delegate()->peerListAppendRow(std::move(row));
  1893. delegate()->peerListRefreshRows();
  1894. }
  1895. void loadMoreRows() override {
  1896. }
  1897. void rowClicked(not_null<PeerListRow*> row) override {
  1898. _activate();
  1899. }
  1900. Main::Session &session() const override {
  1901. return *_session;
  1902. }
  1903. private:
  1904. const not_null<Main::Session*> _session;
  1905. Fn<void()> _activate;
  1906. };
  1907. auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
  1908. const auto container = result.data();
  1909. Ui::AddSkip(container);
  1910. const auto delegate = container->lifetime().make_state<
  1911. PeerListContentDelegateSimple
  1912. >();
  1913. const auto controller = container->lifetime().make_state<
  1914. SelfController
  1915. >(session, activate);
  1916. controller->setStyleOverrides(&st::peerListSingleRow);
  1917. const auto content = container->add(object_ptr<PeerListContent>(
  1918. container,
  1919. controller));
  1920. delegate->setContent(content);
  1921. controller->setDelegate(delegate);
  1922. Ui::AddSkip(container);
  1923. container->add(CreatePeerListSectionSubtitle(
  1924. container,
  1925. tr::lng_contacts_header()));
  1926. const auto overrideKey = [=](int direction, int from, int to) {
  1927. if (!content->isVisible()) {
  1928. return false;
  1929. } else if (direction > 0 && from < 0 && to >= 0) {
  1930. if (content->hasSelection()) {
  1931. const auto was = content->selectedIndex();
  1932. const auto now = content->selectSkip(1).reallyMovedTo;
  1933. if (was != now) {
  1934. return true;
  1935. }
  1936. content->clearSelection();
  1937. } else {
  1938. content->selectSkip(1);
  1939. return true;
  1940. }
  1941. } else if (direction < 0 && to < 0) {
  1942. if (!content->hasSelection()) {
  1943. content->selectLast();
  1944. } else if (from >= 0 || content->hasSelection()) {
  1945. content->selectSkip(-1);
  1946. }
  1947. }
  1948. return false;
  1949. };
  1950. return {
  1951. .content = std::move(result),
  1952. .overrideKey = overrideKey,
  1953. .activate = activate,
  1954. };
  1955. }
  1956. Controller::Controller(
  1957. not_null<Main::Session*> session,
  1958. Fn<void(not_null<PeerData*>)> choose)
  1959. : ContactsBoxController(session)
  1960. , _choose(std::move(choose))
  1961. , _selfOption(MakeSelfOption(session, [=] { _choose(session->user()); })) {
  1962. setStyleOverrides(&st::peerListSmallSkips);
  1963. }
  1964. void Controller::noSearchSubmit() {
  1965. if (const auto onstack = _selfOption.activate) {
  1966. onstack();
  1967. }
  1968. }
  1969. bool Controller::overrideKeyboardNavigation(
  1970. int direction,
  1971. int fromIndex,
  1972. int toIndex) {
  1973. return _selfOption.overrideKey
  1974. && _selfOption.overrideKey(direction, fromIndex, toIndex);
  1975. }
  1976. std::unique_ptr<PeerListRow> Controller::createRow(
  1977. not_null<UserData*> user) {
  1978. if (user->isSelf()
  1979. || user->isBot()
  1980. || user->isServiceUser()
  1981. || user->isInaccessible()) {
  1982. return nullptr;
  1983. }
  1984. return ContactsBoxController::createRow(user);
  1985. }
  1986. void Controller::prepareViewHook() {
  1987. delegate()->peerListSetAboveWidget(std::move(_selfOption.content));
  1988. }
  1989. void Controller::rowClicked(not_null<PeerListRow*> row) {
  1990. _choose(row->peer());
  1991. }
  1992. } // namespace
  1993. void ChooseStarGiftRecipient(
  1994. not_null<Window::SessionController*> window) {
  1995. auto controller = std::make_unique<Controller>(
  1996. &window->session(),
  1997. [=](not_null<PeerData*> peer) {
  1998. ShowStarGiftBox(window, peer);
  1999. });
  2000. const auto controllerRaw = controller.get();
  2001. auto initBox = [=](not_null<PeerListBox*> box) {
  2002. box->setTitle(tr::lng_gift_premium_or_stars());
  2003. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  2004. box->noSearchSubmits() | rpl::start_with_next([=] {
  2005. controllerRaw->noSearchSubmit();
  2006. }, box->lifetime());
  2007. };
  2008. window->show(
  2009. Box<PeerListBox>(std::move(controller), std::move(initBox)),
  2010. LayerOption::KeepOther);
  2011. }
  2012. void ShowStarGiftBox(
  2013. not_null<Window::SessionController*> controller,
  2014. not_null<PeerData*> peer) {
  2015. struct Session {
  2016. PeerData *peer = nullptr;
  2017. bool premiumGiftsReady = false;
  2018. bool starsGiftsReady = false;
  2019. rpl::lifetime lifetime;
  2020. };
  2021. static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
  2022. const auto session = &controller->session();
  2023. auto i = Map.find(session);
  2024. if (i == end(Map)) {
  2025. i = Map.emplace(session).first;
  2026. session->lifetime().add([=] { Map.remove(session); });
  2027. } else if (i->second.peer == peer) {
  2028. return;
  2029. }
  2030. i->second = Session{ .peer = peer };
  2031. const auto weak = base::make_weak(controller);
  2032. const auto show = [=] {
  2033. Map[session] = Session();
  2034. if (const auto strong = weak.get()) {
  2035. strong->show(Box(GiftBox, strong, peer));
  2036. }
  2037. };
  2038. base::timer_once(
  2039. kGiftsPreloadTimeout
  2040. ) | rpl::start_with_next(show, i->second.lifetime);
  2041. const auto user = peer->asUser();
  2042. if (user && !user->isSelf()) {
  2043. GiftsPremium(
  2044. session,
  2045. peer
  2046. ) | rpl::start_with_next([=](PremiumGiftsDescriptor &&gifts) {
  2047. if (!gifts.list.empty()) {
  2048. auto &entry = Map[session];
  2049. entry.premiumGiftsReady = true;
  2050. if (entry.starsGiftsReady) {
  2051. show();
  2052. }
  2053. }
  2054. }, i->second.lifetime);
  2055. } else {
  2056. i->second.premiumGiftsReady = true;
  2057. }
  2058. GiftsStars(
  2059. session,
  2060. peer
  2061. ) | rpl::start_with_next([=](std::vector<GiftTypeStars> &&gifts) {
  2062. if (!gifts.empty()) {
  2063. auto &entry = Map[session];
  2064. entry.starsGiftsReady = true;
  2065. if (entry.premiumGiftsReady) {
  2066. show();
  2067. }
  2068. }
  2069. }, i->second.lifetime);
  2070. }
  2071. void AddUniqueGiftCover(
  2072. not_null<VerticalLayout*> container,
  2073. rpl::producer<Data::UniqueGift> data,
  2074. rpl::producer<QString> subtitleOverride) {
  2075. const auto cover = container->add(object_ptr<RpWidget>(container));
  2076. const auto title = CreateChild<FlatLabel>(
  2077. cover,
  2078. rpl::duplicate(
  2079. data
  2080. ) | rpl::map([](const Data::UniqueGift &now) { return now.title; }),
  2081. st::uniqueGiftTitle);
  2082. title->setTextColorOverride(QColor(255, 255, 255));
  2083. auto subtitleText = subtitleOverride
  2084. ? std::move(subtitleOverride)
  2085. : rpl::duplicate(data) | rpl::map([](const Data::UniqueGift &gift) {
  2086. return tr::lng_gift_unique_number(
  2087. tr::now,
  2088. lt_index,
  2089. QString::number(gift.number));
  2090. });
  2091. const auto subtitle = CreateChild<FlatLabel>(
  2092. cover,
  2093. std::move(subtitleText),
  2094. st::uniqueGiftSubtitle);
  2095. struct GiftView {
  2096. QImage gradient;
  2097. std::optional<Data::UniqueGift> gift;
  2098. std::shared_ptr<Data::DocumentMedia> media;
  2099. std::unique_ptr<Lottie::SinglePlayer> lottie;
  2100. std::unique_ptr<Text::CustomEmoji> emoji;
  2101. base::flat_map<float64, QImage> emojis;
  2102. rpl::lifetime lifetime;
  2103. };
  2104. struct State {
  2105. GiftView now;
  2106. GiftView next;
  2107. Animations::Simple crossfade;
  2108. bool animating = false;
  2109. };
  2110. const auto state = cover->lifetime().make_state<State>();
  2111. const auto lottieSize = st::creditsHistoryEntryStarGiftSize;
  2112. const auto updateColors = [=](float64 progress) {
  2113. subtitle->setTextColorOverride((progress == 0.)
  2114. ? state->now.gift->backdrop.textColor
  2115. : (progress == 1.)
  2116. ? state->next.gift->backdrop.textColor
  2117. : anim::color(
  2118. state->now.gift->backdrop.textColor,
  2119. state->next.gift->backdrop.textColor,
  2120. progress));
  2121. };
  2122. std::move(
  2123. data
  2124. ) | rpl::start_with_next([=](const Data::UniqueGift &gift) {
  2125. const auto setup = [&](GiftView &to) {
  2126. to.gift = gift;
  2127. const auto document = gift.model.document;
  2128. to.media = document->createMediaView();
  2129. to.media->automaticLoad({}, nullptr);
  2130. rpl::single() | rpl::then(
  2131. document->session().downloaderTaskFinished()
  2132. ) | rpl::filter([&to] {
  2133. return to.media->loaded();
  2134. }) | rpl::start_with_next([=, &to] {
  2135. const auto lottieSize = st::creditsHistoryEntryStarGiftSize;
  2136. to.lottie = ChatHelpers::LottiePlayerFromDocument(
  2137. to.media.get(),
  2138. ChatHelpers::StickerLottieSize::MessageHistory,
  2139. QSize(lottieSize, lottieSize),
  2140. Lottie::Quality::High);
  2141. to.lifetime.destroy();
  2142. const auto lottie = to.lottie.get();
  2143. lottie->updates() | rpl::start_with_next([=] {
  2144. if (state->now.lottie.get() == lottie
  2145. || state->crossfade.animating()) {
  2146. cover->update();
  2147. }
  2148. }, to.lifetime);
  2149. }, to.lifetime);
  2150. to.emoji = document->owner().customEmojiManager().create(
  2151. gift.pattern.document,
  2152. [=] { cover->update(); },
  2153. Data::CustomEmojiSizeTag::Large);
  2154. [[maybe_unused]] const auto preload = to.emoji->ready();
  2155. };
  2156. if (!state->now.gift) {
  2157. setup(state->now);
  2158. cover->update();
  2159. updateColors(0.);
  2160. } else if (!state->next.gift) {
  2161. setup(state->next);
  2162. }
  2163. }, cover->lifetime());
  2164. cover->widthValue() | rpl::start_with_next([=](int width) {
  2165. const auto skip = st::uniqueGiftBottom;
  2166. if (width <= 3 * skip) {
  2167. return;
  2168. }
  2169. const auto available = width - 2 * skip;
  2170. title->resizeToWidth(available);
  2171. title->moveToLeft(skip, st::uniqueGiftTitleTop);
  2172. subtitle->resizeToWidth(available);
  2173. subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop);
  2174. cover->resize(width, subtitle->y() + subtitle->height() + skip);
  2175. }, cover->lifetime());
  2176. cover->paintRequest() | rpl::start_with_next([=] {
  2177. auto p = QPainter(cover);
  2178. auto progress = state->crossfade.value(state->animating ? 1. : 0.);
  2179. if (state->animating) {
  2180. updateColors(progress);
  2181. }
  2182. if (progress == 1.) {
  2183. state->animating = false;
  2184. state->now = base::take(state->next);
  2185. progress = 0.;
  2186. }
  2187. const auto paint = [&](GiftView &gift, float64 shown) {
  2188. Expects(gift.gift.has_value());
  2189. const auto width = cover->width();
  2190. const auto pointsHeight = st::uniqueGiftSubtitleTop;
  2191. const auto ratio = style::DevicePixelRatio();
  2192. if (gift.gradient.size() != cover->size() * ratio) {
  2193. gift.gradient = CreateGradient(cover->size(), *gift.gift);
  2194. }
  2195. p.drawImage(0, 0, gift.gradient);
  2196. PaintPoints(
  2197. p,
  2198. PatternPoints(),
  2199. gift.emojis,
  2200. gift.emoji.get(),
  2201. *gift.gift,
  2202. QRect(0, 0, width, pointsHeight),
  2203. shown);
  2204. const auto lottie = gift.lottie.get();
  2205. const auto factor = style::DevicePixelRatio();
  2206. const auto request = Lottie::FrameRequest{
  2207. .box = Size(lottieSize) * factor,
  2208. };
  2209. const auto frame = (lottie && lottie->ready())
  2210. ? lottie->frameInfo(request)
  2211. : Lottie::Animation::FrameInfo();
  2212. if (frame.image.isNull()) {
  2213. return false;
  2214. }
  2215. const auto size = frame.image.size() / factor;
  2216. const auto left = (width - size.width()) / 2;
  2217. p.drawImage(
  2218. QRect(QPoint(left, st::uniqueGiftModelTop), size),
  2219. frame.image);
  2220. const auto count = lottie->framesCount();
  2221. const auto finished = lottie->frameIndex() == (count - 1);
  2222. lottie->markFrameShown();
  2223. return finished;
  2224. };
  2225. if (progress < 1.) {
  2226. const auto finished = paint(state->now, 1. - progress);
  2227. const auto next = finished ? state->next.lottie.get() : nullptr;
  2228. if (next && next->ready()) {
  2229. state->animating = true;
  2230. state->crossfade.start([=] {
  2231. cover->update();
  2232. }, 0., 1., kCrossfadeDuration);
  2233. }
  2234. }
  2235. if (progress > 0.) {
  2236. p.setOpacity(progress);
  2237. paint(state->next, progress);
  2238. }
  2239. }, cover->lifetime());
  2240. }
  2241. void AddWearGiftCover(
  2242. not_null<VerticalLayout*> container,
  2243. const Data::UniqueGift &data,
  2244. not_null<PeerData*> peer) {
  2245. const auto cover = container->add(object_ptr<RpWidget>(container));
  2246. const auto title = CreateChild<FlatLabel>(
  2247. cover,
  2248. rpl::single(peer->name()),
  2249. st::uniqueGiftTitle);
  2250. title->setTextColorOverride(QColor(255, 255, 255));
  2251. const auto subtitle = CreateChild<FlatLabel>(
  2252. cover,
  2253. (peer->isChannel()
  2254. ? tr::lng_chat_status_subscribers(
  2255. lt_count,
  2256. rpl::single(peer->asChannel()->membersCount() * 1.))
  2257. : tr::lng_status_online()),
  2258. st::uniqueGiftSubtitle);
  2259. subtitle->setTextColorOverride(data.backdrop.textColor);
  2260. struct State {
  2261. QImage gradient;
  2262. Data::UniqueGift gift;
  2263. Ui::PeerUserpicView view;
  2264. std::unique_ptr<Text::CustomEmoji> emoji;
  2265. base::flat_map<float64, QImage> emojis;
  2266. rpl::lifetime lifetime;
  2267. };
  2268. const auto state = cover->lifetime().make_state<State>(State{
  2269. .gift = data,
  2270. });
  2271. state->emoji = peer->owner().customEmojiManager().create(
  2272. state->gift.pattern.document,
  2273. [=] { cover->update(); },
  2274. Data::CustomEmojiSizeTag::Large);
  2275. cover->widthValue() | rpl::start_with_next([=](int width) {
  2276. const auto skip = st::uniqueGiftBottom;
  2277. if (width <= 3 * skip) {
  2278. return;
  2279. }
  2280. const auto available = width - 2 * skip;
  2281. title->resizeToWidth(available);
  2282. title->moveToLeft(skip, st::uniqueGiftTitleTop);
  2283. subtitle->resizeToWidth(available);
  2284. subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop);
  2285. cover->resize(width, subtitle->y() + subtitle->height() + skip);
  2286. }, cover->lifetime());
  2287. cover->paintRequest() | rpl::start_with_next([=] {
  2288. auto p = Painter(cover);
  2289. const auto width = cover->width();
  2290. const auto pointsHeight = st::uniqueGiftSubtitleTop;
  2291. const auto ratio = style::DevicePixelRatio();
  2292. if (state->gradient.size() != cover->size() * ratio) {
  2293. state->gradient = CreateGradient(cover->size(), state->gift);
  2294. }
  2295. p.drawImage(0, 0, state->gradient);
  2296. PaintPoints(
  2297. p,
  2298. PatternPoints(),
  2299. state->emojis,
  2300. state->emoji.get(),
  2301. state->gift,
  2302. QRect(0, 0, width, pointsHeight),
  2303. 1.);
  2304. peer->paintUserpic(
  2305. p,
  2306. state->view,
  2307. (width - st::uniqueGiftUserpicSize) / 2,
  2308. st::uniqueGiftUserpicTop,
  2309. st::uniqueGiftUserpicSize);
  2310. }, cover->lifetime());
  2311. }
  2312. void ShowUniqueGiftWearBox(
  2313. std::shared_ptr<ChatHelpers::Show> show,
  2314. not_null<PeerData*> peer,
  2315. const Data::UniqueGift &gift,
  2316. Settings::GiftWearBoxStyleOverride st) {
  2317. show->show(Box([=](not_null<Ui::GenericBox*> box) {
  2318. box->setNoContentMargin(true);
  2319. box->setWidth((st::boxWidth + st::boxWideWidth) / 2); // =)
  2320. box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
  2321. const auto channel = peer->isChannel();
  2322. const auto content = box->verticalLayout();
  2323. AddWearGiftCover(content, gift, peer);
  2324. AddSkip(content, st::defaultVerticalListSkip * 2);
  2325. const auto infoRow = [&](
  2326. rpl::producer<QString> title,
  2327. rpl::producer<QString> text,
  2328. not_null<const style::icon*> icon) {
  2329. auto raw = content->add(
  2330. object_ptr<Ui::VerticalLayout>(content));
  2331. raw->add(
  2332. object_ptr<Ui::FlatLabel>(
  2333. raw,
  2334. std::move(title) | Ui::Text::ToBold(),
  2335. st.infoTitle ? *st.infoTitle : st::defaultFlatLabel),
  2336. st::settingsPremiumRowTitlePadding);
  2337. raw->add(
  2338. object_ptr<Ui::FlatLabel>(
  2339. raw,
  2340. std::move(text),
  2341. st.infoAbout ? *st.infoAbout : st::upgradeGiftSubtext),
  2342. st::settingsPremiumRowAboutPadding);
  2343. object_ptr<Info::Profile::FloatingIcon>(
  2344. raw,
  2345. *icon,
  2346. st::starrefInfoIconPosition);
  2347. };
  2348. content->add(
  2349. object_ptr<Ui::FlatLabel>(
  2350. content,
  2351. tr::lng_gift_wear_title(
  2352. lt_name,
  2353. rpl::single(UniqueGiftName(gift))),
  2354. st.title ? *st.title : st::uniqueGiftTitle),
  2355. st::settingsPremiumRowTitlePadding);
  2356. content->add(
  2357. object_ptr<Ui::FlatLabel>(
  2358. content,
  2359. tr::lng_gift_wear_about(),
  2360. st.subtitle ? *st.subtitle : st::uniqueGiftSubtitle),
  2361. st::settingsPremiumRowAboutPadding);
  2362. infoRow(
  2363. tr::lng_gift_wear_badge_title(),
  2364. (channel
  2365. ? tr::lng_gift_wear_badge_about_channel()
  2366. : tr::lng_gift_wear_badge_about()),
  2367. st.radiantIcon ? st.radiantIcon : &st::menuIconUnique);
  2368. //infoRow(
  2369. // tr::lng_gift_wear_design_title(),
  2370. // tr::lng_gift_wear_design_about(),
  2371. // &st::menuIconUniqueProfile);
  2372. infoRow(
  2373. tr::lng_gift_wear_proof_title(),
  2374. (channel
  2375. ? tr::lng_gift_wear_proof_about_channel()
  2376. : tr::lng_gift_wear_proof_about()),
  2377. st.proofIcon ? st.proofIcon : &st::menuIconFactcheck);
  2378. const auto session = &show->session();
  2379. const auto checking = std::make_shared<bool>();
  2380. const auto button = box->addButton(rpl::single(QString()), [=] {
  2381. const auto emojiStatuses = &session->data().emojiStatuses();
  2382. const auto id = emojiStatuses->fromUniqueGift(gift);
  2383. if (!peer->isSelf()) {
  2384. if (*checking) {
  2385. return;
  2386. }
  2387. *checking = true;
  2388. const auto weak = Ui::MakeWeak(box);
  2389. CheckBoostLevel(show, peer, [=](int level) {
  2390. const auto limits = Data::LevelLimits(&peer->session());
  2391. const auto wanted = limits.channelEmojiStatusLevelMin();
  2392. if (level >= wanted) {
  2393. if (const auto strong = weak.data()) {
  2394. strong->closeBox();
  2395. }
  2396. emojiStatuses->set(peer, id);
  2397. return std::optional<Ui::AskBoostReason>();
  2398. }
  2399. const auto reason = [&]() -> Ui::AskBoostReason {
  2400. return { Ui::AskBoostWearCollectible{
  2401. wanted
  2402. } };
  2403. }();
  2404. return std::make_optional(reason);
  2405. }, [=] { *checking = false; });
  2406. } else if (session->premium()) {
  2407. box->closeBox();
  2408. emojiStatuses->set(peer, id);
  2409. } else {
  2410. const auto link = Ui::Text::Bold(
  2411. tr::lng_send_as_premium_required_link(tr::now));
  2412. Settings::ShowPremiumPromoToast(
  2413. show,
  2414. tr::lng_gift_wear_subscribe(
  2415. tr::now,
  2416. lt_link,
  2417. Ui::Text::Link(link),
  2418. Ui::Text::WithEntities),
  2419. u"wear_collectibles"_q);
  2420. }
  2421. });
  2422. const auto lock = Ui::Text::SingleCustomEmoji(
  2423. session->data().customEmojiManager().registerInternalEmoji(
  2424. st::historySendDisabledIcon,
  2425. st::giftBoxLockMargins,
  2426. true));
  2427. auto label = rpl::combine(
  2428. tr::lng_gift_wear_start(),
  2429. Data::AmPremiumValue(&show->session())
  2430. ) | rpl::map([=](const QString &text, bool premium) {
  2431. auto result = TextWithEntities();
  2432. if (!premium && peer->isSelf()) {
  2433. result.append(lock);
  2434. }
  2435. result.append(text);
  2436. return result;
  2437. });
  2438. SetButtonMarkedLabel(
  2439. button,
  2440. std::move(label),
  2441. session,
  2442. st::creditsBoxButtonLabel,
  2443. &st::giftBox.button.textFg);
  2444. rpl::combine(
  2445. box->widthValue(),
  2446. button->widthValue()
  2447. ) | rpl::start_with_next([=](int outer, int inner) {
  2448. const auto padding = st::giftBox.buttonPadding;
  2449. const auto wanted = outer - padding.left() - padding.right();
  2450. if (inner != wanted) {
  2451. button->resizeToWidth(wanted);
  2452. button->moveToLeft(padding.left(), padding.top());
  2453. }
  2454. }, box->lifetime());
  2455. AddUniqueCloseButton(box, {});
  2456. }));
  2457. }
  2458. struct UpgradeArgs : StarGiftUpgradeArgs {
  2459. std::vector<Data::UniqueGiftModel> models;
  2460. std::vector<Data::UniqueGiftPattern> patterns;
  2461. std::vector<Data::UniqueGiftBackdrop> backdrops;
  2462. };
  2463. [[nodiscard]] rpl::producer<Data::UniqueGift> MakeUpgradeGiftStream(
  2464. const UpgradeArgs &args) {
  2465. if (args.models.empty()
  2466. || args.patterns.empty()
  2467. || args.backdrops.empty()) {
  2468. return rpl::never<Data::UniqueGift>();
  2469. }
  2470. return [=](auto consumer) {
  2471. auto lifetime = rpl::lifetime();
  2472. struct State {
  2473. UpgradeArgs data;
  2474. std::vector<int> modelIndices;
  2475. std::vector<int> patternIndices;
  2476. std::vector<int> backdropIndices;
  2477. };
  2478. const auto state = lifetime.make_state<State>(State{
  2479. .data = args,
  2480. });
  2481. const auto put = [=] {
  2482. const auto index = [](std::vector<int> &indices, const auto &v) {
  2483. const auto fill = [&] {
  2484. if (!indices.empty()) {
  2485. return;
  2486. }
  2487. indices = ranges::views::ints(0) | ranges::views::take(
  2488. v.size()
  2489. ) | ranges::to_vector;
  2490. ranges::shuffle(indices);
  2491. };
  2492. fill();
  2493. const auto result = indices.back();
  2494. indices.pop_back();
  2495. fill();
  2496. if (indices.back() == result) {
  2497. std::swap(indices.front(), indices.back());
  2498. }
  2499. return result;
  2500. };
  2501. auto &models = state->data.models;
  2502. auto &patterns = state->data.patterns;
  2503. auto &backdrops = state->data.backdrops;
  2504. consumer.put_next(Data::UniqueGift{
  2505. .title = (state->data.savedId
  2506. ? tr::lng_gift_upgrade_title(tr::now)
  2507. : tr::lng_gift_upgrade_preview_title(tr::now)),
  2508. .model = models[index(state->modelIndices, models)],
  2509. .pattern = patterns[index(state->patternIndices, patterns)],
  2510. .backdrop = backdrops[index(state->backdropIndices, backdrops)],
  2511. });
  2512. };
  2513. put();
  2514. base::timer_each(
  2515. kSwitchUpgradeCoverInterval / 3
  2516. ) | rpl::start_with_next(put, lifetime);
  2517. return lifetime;
  2518. };
  2519. }
  2520. void AddUpgradeGiftCover(
  2521. not_null<VerticalLayout*> container,
  2522. const UpgradeArgs &args) {
  2523. AddUniqueGiftCover(
  2524. container,
  2525. MakeUpgradeGiftStream(args),
  2526. (args.savedId
  2527. ? tr::lng_gift_upgrade_about()
  2528. : (args.peer->isBroadcast()
  2529. ? tr::lng_gift_upgrade_preview_about_channel
  2530. : tr::lng_gift_upgrade_preview_about)(
  2531. lt_name,
  2532. rpl::single(args.peer->shortName()))));
  2533. }
  2534. void UpgradeBox(
  2535. not_null<GenericBox*> box,
  2536. not_null<Window::SessionController*> controller,
  2537. UpgradeArgs &&args) {
  2538. box->setNoContentMargin(true);
  2539. const auto container = box->verticalLayout();
  2540. AddUpgradeGiftCover(container, args);
  2541. AddSkip(container, st::defaultVerticalListSkip * 2);
  2542. const auto infoRow = [&](
  2543. rpl::producer<QString> title,
  2544. rpl::producer<QString> text,
  2545. not_null<const style::icon*> icon) {
  2546. auto raw = container->add(
  2547. object_ptr<Ui::VerticalLayout>(container));
  2548. raw->add(
  2549. object_ptr<Ui::FlatLabel>(
  2550. raw,
  2551. std::move(title) | Ui::Text::ToBold(),
  2552. st::defaultFlatLabel),
  2553. st::settingsPremiumRowTitlePadding);
  2554. raw->add(
  2555. object_ptr<Ui::FlatLabel>(
  2556. raw,
  2557. std::move(text),
  2558. st::upgradeGiftSubtext),
  2559. st::settingsPremiumRowAboutPadding);
  2560. object_ptr<Info::Profile::FloatingIcon>(
  2561. raw,
  2562. *icon,
  2563. st::starrefInfoIconPosition);
  2564. };
  2565. infoRow(
  2566. tr::lng_gift_upgrade_unique_title(),
  2567. tr::lng_gift_upgrade_unique_about(),
  2568. &st::menuIconUnique);
  2569. infoRow(
  2570. tr::lng_gift_upgrade_transferable_title(),
  2571. tr::lng_gift_upgrade_transferable_about(),
  2572. &st::menuIconReplace);
  2573. infoRow(
  2574. tr::lng_gift_upgrade_tradable_title(),
  2575. tr::lng_gift_upgrade_tradable_about(),
  2576. &st::menuIconTradable);
  2577. struct State {
  2578. bool sent = false;
  2579. bool preserveDetails = false;
  2580. };
  2581. const auto state = std::make_shared<State>();
  2582. const auto preview = !args.savedId;
  2583. if (!preview) {
  2584. const auto skip = st::defaultVerticalListSkip;
  2585. container->add(
  2586. object_ptr<PlainShadow>(container),
  2587. st::boxRowPadding + QMargins(0, skip, 0, skip));
  2588. const auto checkbox = container->add(
  2589. object_ptr<CenterWrap<Checkbox>>(
  2590. container,
  2591. object_ptr<Checkbox>(
  2592. container,
  2593. (args.canAddComment
  2594. ? tr::lng_gift_upgrade_add_comment(tr::now)
  2595. : args.canAddSender
  2596. ? tr::lng_gift_upgrade_add_sender(tr::now)
  2597. : args.canAddMyComment
  2598. ? tr::lng_gift_upgrade_add_my_comment(tr::now)
  2599. : tr::lng_gift_upgrade_add_my(tr::now)),
  2600. args.addDetailsDefault)),
  2601. st::defaultCheckbox.margin)->entity();
  2602. checkbox->checkedChanges() | rpl::start_with_next([=](bool checked) {
  2603. state->preserveDetails = checked;
  2604. }, checkbox->lifetime());
  2605. }
  2606. box->setStyle(preview ? st::giftBox : st::upgradeGiftBox);
  2607. const auto cost = args.cost;
  2608. const auto session = &controller->session();
  2609. auto buttonText = preview ? tr::lng_box_ok() : rpl::single(QString());
  2610. const auto button = box->addButton(std::move(buttonText), [=] {
  2611. if (preview) {
  2612. box->closeBox();
  2613. return;
  2614. } else if (state->sent) {
  2615. return;
  2616. }
  2617. state->sent = true;
  2618. const auto keepDetails = state->preserveDetails;
  2619. const auto weak = Ui::MakeWeak(box);
  2620. const auto done = [=](Payments::CheckoutResult result) {
  2621. if (result != Payments::CheckoutResult::Paid) {
  2622. state->sent = false;
  2623. } else {
  2624. controller->showPeerHistory(args.peer);
  2625. if (const auto strong = weak.data()) {
  2626. strong->closeBox();
  2627. }
  2628. }
  2629. };
  2630. UpgradeGift(controller, args.savedId, keepDetails, cost, done);
  2631. });
  2632. if (!preview) {
  2633. auto star = session->data().customEmojiManager().creditsEmoji();
  2634. SetButtonMarkedLabel(
  2635. button,
  2636. (cost
  2637. ? tr::lng_gift_upgrade_button(
  2638. lt_price,
  2639. rpl::single(star.append(
  2640. ' ' + Lang::FormatStarsAmountDecimal(
  2641. StarsAmount{ cost }))),
  2642. Ui::Text::WithEntities)
  2643. : tr::lng_gift_upgrade_confirm(Ui::Text::WithEntities)),
  2644. &controller->session(),
  2645. st::creditsBoxButtonLabel,
  2646. &st::giftBox.button.textFg);
  2647. }
  2648. rpl::combine(
  2649. box->widthValue(),
  2650. button->widthValue()
  2651. ) | rpl::start_with_next([=](int outer, int inner) {
  2652. const auto padding = st::giftBox.buttonPadding;
  2653. const auto wanted = outer - padding.left() - padding.right();
  2654. if (inner != wanted) {
  2655. button->resizeToWidth(wanted);
  2656. button->moveToLeft(padding.left(), padding.top());
  2657. }
  2658. }, box->lifetime());
  2659. AddUniqueCloseButton(box, {});
  2660. }
  2661. const std::vector<PatternPoint> &PatternPoints() {
  2662. static const auto kSmall = 0.7;
  2663. static const auto kFaded = 0.2;
  2664. static const auto kLarge = 0.85;
  2665. static const auto kOpaque = 0.3;
  2666. static const auto result = std::vector<PatternPoint>{
  2667. { { 0.5, 0.066 }, kSmall, kFaded },
  2668. { { 0.177, 0.168 }, kSmall, kFaded },
  2669. { { 0.822, 0.168 }, kSmall, kFaded },
  2670. { { 0.37, 0.168 }, kLarge, kOpaque },
  2671. { { 0.63, 0.168 }, kLarge, kOpaque },
  2672. { { 0.277, 0.308 }, kSmall, kOpaque },
  2673. { { 0.723, 0.308 }, kSmall, kOpaque },
  2674. { { 0.13, 0.42 }, kSmall, kFaded },
  2675. { { 0.87, 0.42 }, kSmall, kFaded },
  2676. { { 0.27, 0.533 }, kLarge, kOpaque },
  2677. { { 0.73, 0.533 }, kLarge, kOpaque },
  2678. { { 0.2, 0.73 }, kSmall, kFaded },
  2679. { { 0.8, 0.73 }, kSmall, kFaded },
  2680. { { 0.302, 0.825 }, kLarge, kOpaque },
  2681. { { 0.698, 0.825 }, kLarge, kOpaque },
  2682. { { 0.5, 0.876 }, kLarge, kFaded },
  2683. { { 0.144, 0.936 }, kSmall, kFaded },
  2684. { { 0.856, 0.936 }, kSmall, kFaded },
  2685. };
  2686. return result;
  2687. }
  2688. const std::vector<PatternPoint> &PatternPointsSmall() {
  2689. static const auto kSmall = 0.45;
  2690. static const auto kFaded = 0.2;
  2691. static const auto kLarge = 0.55;
  2692. static const auto kOpaque = 0.3;
  2693. static const auto result = std::vector<PatternPoint>{
  2694. { { 0.5, 0.066 }, kSmall, kFaded },
  2695. { { 0.177, 0.168 }, kSmall, kFaded },
  2696. { { 0.822, 0.168 }, kSmall, kFaded },
  2697. { { 0.37, 0.168 }, kLarge, kOpaque },
  2698. { { 0.63, 0.168 }, kLarge, kOpaque },
  2699. { { 0.277, 0.308 }, kSmall, kOpaque },
  2700. { { 0.723, 0.308 }, kSmall, kOpaque },
  2701. { { 0.13, 0.42 }, kSmall, kFaded },
  2702. { { 0.87, 0.42 }, kSmall, kFaded },
  2703. { { 0.27, 0.533 }, kLarge, kOpaque },
  2704. { { 0.73, 0.533 }, kLarge, kOpaque },
  2705. { { 0.2, 0.73 }, kSmall, kFaded },
  2706. { { 0.8, 0.73 }, kSmall, kFaded },
  2707. { { 0.302, 0.825 }, kLarge, kOpaque },
  2708. { { 0.698, 0.825 }, kLarge, kOpaque },
  2709. { { 0.5, 0.876 }, kLarge, kFaded },
  2710. { { 0.144, 0.936 }, kSmall, kFaded },
  2711. { { 0.856, 0.936 }, kSmall, kFaded },
  2712. };
  2713. return result;
  2714. }
  2715. void PaintPoints(
  2716. QPainter &p,
  2717. const std::vector<PatternPoint> &points,
  2718. base::flat_map<float64, QImage> &cache,
  2719. not_null<Text::CustomEmoji*> emoji,
  2720. const Data::UniqueGift &gift,
  2721. const QRect &rect,
  2722. float64 shown) {
  2723. const auto origin = rect.topLeft();
  2724. const auto width = rect.width();
  2725. const auto height = rect.height();
  2726. const auto ratio = style::DevicePixelRatio();
  2727. const auto paintPoint = [&](const PatternPoint &point) {
  2728. const auto key = (1. + point.opacity) * 10. + point.scale;
  2729. auto &image = cache[key];
  2730. PrepareImage(image, emoji, point, gift);
  2731. if (!image.isNull()) {
  2732. const auto position = origin + QPoint(
  2733. int(point.position.x() * width),
  2734. int(point.position.y() * height));
  2735. if (shown < 1.) {
  2736. p.save();
  2737. p.translate(position);
  2738. p.scale(shown, shown);
  2739. p.translate(-position);
  2740. }
  2741. const auto size = image.size() / ratio;
  2742. p.drawImage(
  2743. position - QPoint(size.width() / 2, size.height() / 2),
  2744. image);
  2745. if (shown < 1.) {
  2746. p.restore();
  2747. }
  2748. }
  2749. };
  2750. for (const auto &point : points) {
  2751. paintPoint(point);
  2752. }
  2753. }
  2754. void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
  2755. const auto weak = base::make_weak(args.controller);
  2756. const auto session = &args.peer->session();
  2757. session->api().request(MTPpayments_GetStarGiftUpgradePreview(
  2758. MTP_long(args.stargiftId)
  2759. )).done([=](const MTPpayments_StarGiftUpgradePreview &result) {
  2760. const auto strong = weak.get();
  2761. if (!strong) {
  2762. if (const auto onstack = args.ready) {
  2763. onstack(false);
  2764. }
  2765. return;
  2766. }
  2767. const auto &data = result.data();
  2768. auto upgrade = UpgradeArgs{ args };
  2769. for (const auto &attribute : data.vsample_attributes().v) {
  2770. attribute.match([&](const MTPDstarGiftAttributeModel &data) {
  2771. upgrade.models.push_back(Api::FromTL(session, data));
  2772. }, [&](const MTPDstarGiftAttributePattern &data) {
  2773. upgrade.patterns.push_back(Api::FromTL(session, data));
  2774. }, [&](const MTPDstarGiftAttributeBackdrop &data) {
  2775. upgrade.backdrops.push_back(Api::FromTL(data));
  2776. }, [](const auto &) {});
  2777. }
  2778. strong->show(Box(UpgradeBox, strong, std::move(upgrade)));
  2779. if (const auto onstack = args.ready) {
  2780. onstack(true);
  2781. }
  2782. }).fail([=](const MTP::Error &error) {
  2783. if (const auto strong = weak.get()) {
  2784. strong->showToast(error.type());
  2785. }
  2786. if (const auto onstack = args.ready) {
  2787. onstack(false);
  2788. }
  2789. }).send();
  2790. }
  2791. void AddUniqueCloseButton(
  2792. not_null<GenericBox*> box,
  2793. Settings::CreditsEntryBoxStyleOverrides st,
  2794. Fn<void(not_null<Ui::PopupMenu*>)> fillMenu) {
  2795. const auto close = Ui::CreateChild<IconButton>(
  2796. box,
  2797. st::uniqueCloseButton);
  2798. const auto menu = fillMenu
  2799. ? Ui::CreateChild<IconButton>(box, st::uniqueMenuButton)
  2800. : nullptr;
  2801. close->show();
  2802. close->raise();
  2803. if (menu) {
  2804. menu->show();
  2805. menu->raise();
  2806. }
  2807. box->widthValue() | rpl::start_with_next([=](int width) {
  2808. close->moveToRight(0, 0, width);
  2809. close->raise();
  2810. if (menu) {
  2811. menu->moveToRight(close->width(), 0, width);
  2812. menu->raise();
  2813. }
  2814. }, close->lifetime());
  2815. close->setClickedCallback([=] {
  2816. box->closeBox();
  2817. });
  2818. if (menu) {
  2819. const auto state = menu->lifetime().make_state<
  2820. base::unique_qptr<Ui::PopupMenu>
  2821. >();
  2822. menu->setClickedCallback([=] {
  2823. if (*state) {
  2824. *state = nullptr;
  2825. return;
  2826. }
  2827. *state = base::make_unique_q<Ui::PopupMenu>(
  2828. menu,
  2829. st.menu ? *st.menu : st::popupMenuWithIcons);
  2830. fillMenu(state->get());
  2831. if (!(*state)->empty()) {
  2832. (*state)->popup(QCursor::pos());
  2833. }
  2834. });
  2835. }
  2836. }
  2837. void RequestStarsFormAndSubmit(
  2838. not_null<Window::SessionController*> window,
  2839. MTPInputInvoice invoice,
  2840. Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done) {
  2841. const auto weak = base::make_weak(window);
  2842. window->session().api().request(MTPpayments_GetPaymentForm(
  2843. MTP_flags(0),
  2844. invoice,
  2845. MTPDataJSON() // theme_params
  2846. )).done([=](const MTPpayments_PaymentForm &result) {
  2847. result.match([&](const MTPDpayments_paymentFormStarGift &data) {
  2848. const auto formId = data.vform_id().v;
  2849. const auto prices = data.vinvoice().data().vprices().v;
  2850. const auto strong = weak.get();
  2851. if (!strong) {
  2852. done(Payments::CheckoutResult::Failed, nullptr);
  2853. return;
  2854. }
  2855. const auto ready = [=](Settings::SmallBalanceResult result) {
  2856. SendStarsFormRequest(strong, result, formId, invoice, done);
  2857. };
  2858. Settings::MaybeRequestBalanceIncrease(
  2859. strong->uiShow(),
  2860. prices.front().data().vamount().v,
  2861. Settings::SmallBalanceDeepLink{},
  2862. ready);
  2863. }, [&](const auto &) {
  2864. done(Payments::CheckoutResult::Failed, nullptr);
  2865. });
  2866. }).fail([=](const MTP::Error &error) {
  2867. const auto type = error.type();
  2868. if (type == u"STARGIFT_EXPORT_IN_PROGRESS"_q) {
  2869. done(Payments::CheckoutResult::Cancelled, nullptr);
  2870. } else {
  2871. if (const auto strong = weak.get()) {
  2872. strong->showToast(type);
  2873. }
  2874. done(Payments::CheckoutResult::Failed, nullptr);
  2875. }
  2876. }).send();
  2877. }
  2878. void ShowGiftTransferredToast(
  2879. base::weak_ptr<Window::SessionController> weak,
  2880. not_null<PeerData*> to,
  2881. const Data::UniqueGift &gift) {
  2882. if (const auto strong = weak.get()) {
  2883. strong->showToast({
  2884. .title = tr::lng_gift_transferred_title(tr::now),
  2885. .text = tr::lng_gift_transferred_about(
  2886. tr::now,
  2887. lt_name,
  2888. Text::Bold(Data::UniqueGiftName(gift)),
  2889. lt_recipient,
  2890. Text::Bold(to->shortName()),
  2891. Ui::Text::WithEntities),
  2892. .duration = kUpgradeDoneToastDuration,
  2893. });
  2894. }
  2895. }
  2896. } // namespace Ui