| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "boxes/star_gift_box.h"
- #include "apiwrap.h"
- #include "api/api_credits.h"
- #include "api/api_premium.h"
- #include "base/event_filter.h"
- #include "base/random.h"
- #include "base/timer_rpl.h"
- #include "base/unixtime.h"
- #include "boxes/filters/edit_filter_chats_list.h"
- #include "boxes/peers/edit_peer_color_box.h"
- #include "boxes/gift_premium_box.h"
- #include "boxes/peer_list_controllers.h"
- #include "boxes/premium_preview_box.h"
- #include "boxes/send_credits_box.h"
- #include "chat_helpers/emoji_suggestions_widget.h"
- #include "chat_helpers/message_field.h"
- #include "chat_helpers/stickers_gift_box_pack.h"
- #include "chat_helpers/stickers_lottie.h"
- #include "chat_helpers/tabbed_panel.h"
- #include "chat_helpers/tabbed_selector.h"
- #include "core/ui_integration.h"
- #include "data/data_changes.h"
- #include "data/data_channel.h"
- #include "data/data_credits.h"
- #include "data/data_document.h"
- #include "data/data_document_media.h"
- #include "data/data_emoji_statuses.h"
- #include "data/data_file_origin.h"
- #include "data/data_peer_values.h"
- #include "data/data_premium_limits.h"
- #include "data/data_session.h"
- #include "data/data_user.h"
- #include "data/stickers/data_custom_emoji.h"
- #include "history/admin_log/history_admin_log_item.h"
- #include "history/view/media/history_view_media_generic.h"
- #include "history/view/media/history_view_unique_gift.h"
- #include "history/view/history_view_element.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/history_item_helpers.h"
- #include "info/peer_gifts/info_peer_gifts_common.h"
- #include "info/profile/info_profile_icon.h"
- #include "lang/lang_keys.h"
- #include "lottie/lottie_common.h"
- #include "lottie/lottie_single_player.h"
- #include "main/main_app_config.h"
- #include "main/main_session.h"
- #include "payments/payments_form.h"
- #include "payments/payments_checkout_process.h"
- #include "payments/payments_non_panel_process.h"
- #include "settings/settings_credits.h"
- #include "settings/settings_credits_graphics.h"
- #include "settings/settings_premium.h"
- #include "ui/boxes/boost_box.h"
- #include "ui/chat/chat_style.h"
- #include "ui/chat/chat_theme.h"
- #include "ui/controls/emoji_button.h"
- #include "ui/controls/userpic_button.h"
- #include "ui/effects/path_shift_gradient.h"
- #include "ui/effects/premium_graphics.h"
- #include "ui/effects/premium_stars_colored.h"
- #include "ui/layers/generic_box.h"
- #include "ui/new_badges.h"
- #include "ui/painter.h"
- #include "ui/rect.h"
- #include "ui/text/format_values.h"
- #include "ui/text/text_utilities.h"
- #include "ui/toast/toast.h"
- #include "ui/ui_utility.h"
- #include "ui/vertical_list.h"
- #include "ui/widgets/fields/input_field.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/checkbox.h"
- #include "ui/widgets/popup_menu.h"
- #include "ui/widgets/shadow.h"
- #include "ui/wrap/slide_wrap.h"
- #include "window/themes/window_theme.h"
- #include "window/section_widget.h"
- #include "window/window_session_controller.h"
- #include "styles/style_boxes.h"
- #include "styles/style_chat.h"
- #include "styles/style_chat_helpers.h"
- #include "styles/style_credits.h"
- #include "styles/style_layers.h"
- #include "styles/style_menu_icons.h"
- #include "styles/style_premium.h"
- #include "styles/style_settings.h"
- #include "styles/style_widgets.h"
- #include <QtWidgets/QApplication>
- namespace Ui {
- namespace {
- constexpr auto kPriceTabAll = 0;
- constexpr auto kPriceTabLimited = -2;
- constexpr auto kPriceTabInStock = -1;
- constexpr auto kGiftMessageLimit = 255;
- constexpr auto kSentToastDuration = 3 * crl::time(1000);
- constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
- constexpr auto kCrossfadeDuration = crl::time(400);
- constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000);
- constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000);
- using namespace HistoryView;
- using namespace Info::PeerGifts;
- struct PremiumGiftsDescriptor {
- std::vector<GiftTypePremium> list;
- std::shared_ptr<Api::PremiumGiftCodeOptions> api;
- };
- struct GiftsDescriptor {
- std::vector<GiftDescriptor> list;
- std::shared_ptr<Api::PremiumGiftCodeOptions> api;
- };
- struct GiftDetails {
- GiftDescriptor descriptor;
- TextWithEntities text;
- uint64 randomId = 0;
- bool anonymous = false;
- bool upgraded = false;
- bool byStars = false;
- };
- class PreviewDelegate final : public DefaultElementDelegate {
- public:
- PreviewDelegate(
- not_null<QWidget*> parent,
- not_null<ChatStyle*> st,
- Fn<void()> update);
- bool elementAnimationsPaused() override;
- not_null<PathShiftGradient*> elementPathShiftGradient() override;
- Context elementContext() override;
- private:
- const not_null<QWidget*> _parent;
- const std::unique_ptr<PathShiftGradient> _pathGradient;
- };
- class PreviewWrap final : public RpWidget {
- public:
- PreviewWrap(
- not_null<QWidget*> parent,
- not_null<PeerData*> recipient,
- rpl::producer<GiftDetails> details);
- ~PreviewWrap();
- private:
- void paintEvent(QPaintEvent *e) override;
- void resizeTo(int width);
- void prepare(rpl::producer<GiftDetails> details);
- const not_null<History*> _history;
- const not_null<PeerData*> _recipient;
- const std::unique_ptr<ChatTheme> _theme;
- const std::unique_ptr<ChatStyle> _style;
- const std::unique_ptr<PreviewDelegate> _delegate;
- AdminLog::OwnedItem _item;
- QPoint _position;
- };
- [[nodiscard]] bool SortForBirthday(not_null<PeerData*> peer) {
- const auto user = peer->asUser();
- if (!user) {
- return false;
- }
- const auto birthday = user->birthday();
- if (!birthday) {
- return false;
- }
- const auto is = [&](const QDate &date) {
- return (date.day() == birthday.day())
- && (date.month() == birthday.month());
- };
- const auto now = QDate::currentDate();
- return is(now) || is(now.addDays(1)) || is(now.addDays(-1));
- }
- [[nodiscard]] bool IsSoldOut(const Data::StarGift &info) {
- return info.limitedCount && !info.limitedLeft;
- }
- PreviewDelegate::PreviewDelegate(
- not_null<QWidget*> parent,
- not_null<ChatStyle*> st,
- Fn<void()> update)
- : _parent(parent)
- , _pathGradient(MakePathShiftGradient(st, update)) {
- }
- bool PreviewDelegate::elementAnimationsPaused() {
- return _parent->window()->isActiveWindow();
- }
- auto PreviewDelegate::elementPathShiftGradient()
- -> not_null<PathShiftGradient*> {
- return _pathGradient.get();
- }
- Context PreviewDelegate::elementContext() {
- return Context::History;
- }
- auto GenerateGiftMedia(
- not_null<Element*> parent,
- Element *replacing,
- not_null<PeerData*> recipient,
- const GiftDetails &data)
- -> Fn<void(
- not_null<MediaGeneric*>,
- Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
- return [=](
- not_null<MediaGeneric*> media,
- Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
- const auto &descriptor = data.descriptor;
- auto pushText = [&](
- TextWithEntities text,
- QMargins margins = {},
- const base::flat_map<uint16, ClickHandlerPtr> &links = {},
- Ui::Text::MarkedContext context = {}) {
- if (text.empty()) {
- return;
- }
- push(std::make_unique<MediaGenericTextPart>(
- std::move(text),
- margins,
- st::defaultTextStyle,
- links,
- std::move(context)));
- };
- const auto sticker = [=] {
- using Tag = ChatHelpers::StickerLottieSize;
- const auto session = &parent->history()->session();
- const auto sticker = LookupGiftSticker(session, descriptor);
- return StickerInBubblePart::Data{
- .sticker = sticker,
- .size = st::chatIntroStickerSize,
- .cacheTag = Tag::ChatIntroHelloSticker,
- .singleTimePlayback = v::is<GiftTypePremium>(descriptor),
- };
- };
- push(std::make_unique<StickerInBubblePart>(
- parent,
- replacing,
- sticker,
- st::giftBoxPreviewStickerPadding));
- auto title = v::match(descriptor, [&](GiftTypePremium gift) {
- return tr::lng_action_gift_premium_months(
- tr::now,
- lt_count,
- gift.months,
- Text::Bold);
- }, [&](const GiftTypeStars &gift) {
- return recipient->isSelf()
- ? tr::lng_action_gift_self_subtitle(tr::now, Text::Bold)
- : tr::lng_action_gift_got_subtitle(
- tr::now,
- lt_user,
- TextWithEntities()
- .append(Text::SingleCustomEmoji(
- recipient->owner().customEmojiManager(
- ).peerUserpicEmojiData(
- recipient->session().user())))
- .append(' ')
- .append(recipient->session().user()->shortName()),
- Text::Bold);
- });
- auto textFallback = v::match(descriptor, [&](GiftTypePremium gift) {
- return tr::lng_action_gift_premium_about(
- tr::now,
- Text::RichLangValue);
- }, [&](const GiftTypeStars &gift) {
- return data.upgraded
- ? tr::lng_action_gift_got_upgradable_text(
- tr::now,
- Text::RichLangValue)
- : (recipient->isSelf() && gift.info.starsToUpgrade)
- ? tr::lng_action_gift_self_about_unique(
- tr::now,
- Text::RichLangValue)
- : (recipient->isBroadcast() && gift.info.starsToUpgrade)
- ? tr::lng_action_gift_channel_about_unique(
- tr::now,
- Text::RichLangValue)
- : (recipient->isSelf()
- ? tr::lng_action_gift_self_about
- : recipient->isBroadcast()
- ? tr::lng_action_gift_channel_about
- : tr::lng_action_gift_got_stars_text)(
- tr::now,
- lt_count,
- gift.info.starsConverted,
- Text::RichLangValue);
- });
- auto description = data.text.empty()
- ? std::move(textFallback)
- : data.text;
- const auto context = Core::TextContext({
- .session = &parent->history()->session(),
- .repaint = [parent] { parent->repaint(); },
- });
- pushText(
- std::move(title),
- st::giftBoxPreviewTitlePadding,
- {},
- context);
- pushText(
- std::move(description),
- st::giftBoxPreviewTextPadding,
- {},
- context);
- push(HistoryView::MakeGenericButtonPart(
- (data.upgraded
- ? tr::lng_gift_view_unpack(tr::now)
- : tr::lng_sticker_premium_view(tr::now)),
- st::giftBoxButtonMargin,
- [parent] { parent->repaint(); },
- nullptr));
- };
- }
- [[nodiscard]] QImage CreateGradient(
- QSize size,
- const Data::UniqueGift &gift) {
- const auto ratio = style::DevicePixelRatio();
- auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
- result.setDevicePixelRatio(ratio);
- auto p = QPainter(&result);
- auto hq = PainterHighQualityEnabler(p);
- auto gradient = QRadialGradient(
- QRect(QPoint(), size).center(),
- size.height() / 2);
- gradient.setStops({
- { 0., gift.backdrop.centerColor },
- { 1., gift.backdrop.edgeColor },
- });
- p.setBrush(gradient);
- p.setPen(Qt::NoPen);
- p.drawRect(QRect(QPoint(), size));
- p.end();
- const auto mask = Images::CornersMask(st::boxRadius);
- return Images::Round(std::move(result), mask, RectPart::FullTop);
- }
- void PrepareImage(
- QImage &image,
- not_null<Text::CustomEmoji*> emoji,
- const PatternPoint &point,
- const Data::UniqueGift &gift) {
- if (!image.isNull() || !emoji->ready()) {
- return;
- }
- const auto ratio = style::DevicePixelRatio();
- const auto size = Emoji::GetSizeNormal() / ratio;
- image = QImage(
- 2 * QSize(size, size) * ratio,
- QImage::Format_ARGB32_Premultiplied);
- image.setDevicePixelRatio(ratio);
- image.fill(Qt::transparent);
- auto p = QPainter(&image);
- auto hq = PainterHighQualityEnabler(p);
- p.setOpacity(point.opacity);
- if (point.scale < 1.) {
- p.translate(size, size);
- p.scale(point.scale, point.scale);
- p.translate(-size, -size);
- }
- const auto shift = (2 * size - (Emoji::GetSizeLarge() / ratio)) / 2;
- emoji->paint(p, {
- .textColor = gift.backdrop.patternColor,
- .position = QPoint(shift, shift),
- });
- }
- PreviewWrap::PreviewWrap(
- not_null<QWidget*> parent,
- not_null<PeerData*> recipient,
- rpl::producer<GiftDetails> details)
- : RpWidget(parent)
- , _history(recipient->owner().history(recipient->session().userPeerId()))
- , _recipient(recipient)
- , _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
- , _style(std::make_unique<ChatStyle>(
- _history->session().colorIndicesValue()))
- , _delegate(std::make_unique<PreviewDelegate>(
- parent,
- _style.get(),
- [=] { update(); }))
- , _position(0, st::msgMargin.bottom()) {
- _style->apply(_theme.get());
- using namespace HistoryView;
- _history->owner().viewRepaintRequest(
- ) | rpl::start_with_next([=](not_null<const Element*> view) {
- if (view == _item.get()) {
- update();
- }
- }, lifetime());
- _history->session().downloaderTaskFinished() | rpl::start_with_next([=] {
- update();
- }, lifetime());
- prepare(std::move(details));
- }
- void ShowSentToast(
- not_null<Window::SessionController*> window,
- const GiftDescriptor &descriptor,
- const GiftDetails &details) {
- const auto &st = st::historyPremiumToast;
- const auto skip = st.padding.top();
- const auto size = st.style.font->height * 2;
- const auto document = LookupGiftSticker(&window->session(), descriptor);
- const auto leftSkip = document
- ? (skip + size + skip - st.padding.left())
- : 0;
- auto text = v::match(descriptor, [&](const GiftTypePremium &gift) {
- return tr::lng_action_gift_premium_about(
- tr::now,
- Text::RichLangValue);
- }, [&](const GiftTypeStars &gift) {
- const auto amount = gift.info.stars
- + (details.upgraded ? gift.info.starsToUpgrade : 0);
- return tr::lng_gift_sent_about(
- tr::now,
- lt_count,
- amount,
- Text::RichLangValue);
- });
- const auto strong = window->showToast({
- .title = tr::lng_gift_sent_title(tr::now),
- .text = std::move(text),
- .padding = rpl::single(QMargins(leftSkip, 0, 0, 0)),
- .st = &st,
- .attach = RectPart::Top,
- .duration = kSentToastDuration,
- }).get();
- if (!strong || !document) {
- return;
- }
- const auto widget = strong->widget();
- const auto preview = CreateChild<RpWidget>(widget.get());
- preview->moveToLeft(skip, skip);
- preview->resize(size, size);
- preview->show();
- const auto bytes = document->createMediaView()->bytes();
- const auto filepath = document->filepath();
- const auto ratio = style::DevicePixelRatio();
- const auto player = preview->lifetime().make_state<Lottie::SinglePlayer>(
- Lottie::ReadContent(bytes, filepath),
- Lottie::FrameRequest{ QSize(size, size) * ratio },
- Lottie::Quality::Default);
- preview->paintRequest(
- ) | rpl::start_with_next([=] {
- if (!player->ready()) {
- return;
- }
- const auto image = player->frame();
- QPainter(preview).drawImage(
- QRect(QPoint(), image.size() / ratio),
- image);
- if (player->frameIndex() + 1 != player->framesCount()) {
- player->markFrameShown();
- }
- }, preview->lifetime());
- player->updates(
- ) | rpl::start_with_next([=] {
- preview->update();
- }, preview->lifetime());
- }
- PreviewWrap::~PreviewWrap() {
- _item = {};
- }
- void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
- std::move(details) | rpl::start_with_next([=](GiftDetails details) {
- const auto &descriptor = details.descriptor;
- const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
- const auto stars = (details.byStars && data.stars)
- ? data.stars
- : (data.currency == kCreditsCurrency)
- ? data.cost
- : 0;
- return stars
- ? tr::lng_gift_stars_title(tr::now, lt_count, stars)
- : FillAmountAndCurrency(data.cost, data.currency, true);
- }, [&](GiftTypeStars data) {
- const auto stars = data.info.stars
- + (details.upgraded ? data.info.starsToUpgrade : 0);
- return stars
- ? tr::lng_gift_stars_title(tr::now, lt_count, stars)
- : QString();
- });
- const auto name = _history->session().user()->shortName();
- const auto text = cost.isEmpty()
- ? tr::lng_action_gift_unique_received(tr::now, lt_user, name)
- : _recipient->isSelf()
- ? tr::lng_action_gift_self_bought(tr::now, lt_cost, cost)
- : _recipient->isBroadcast()
- ? tr::lng_action_gift_sent_channel(
- tr::now,
- lt_user,
- name,
- lt_name,
- _recipient->name(),
- lt_cost,
- cost)
- : tr::lng_action_gift_received(
- tr::now,
- lt_user,
- name,
- lt_cost,
- cost);
- const auto item = _history->makeMessage({
- .id = _history->nextNonHistoryEntryId(),
- .flags = (MessageFlag::FakeAboutView
- | MessageFlag::FakeHistoryItem
- | MessageFlag::Local),
- .from = _history->peer->id,
- }, PreparedServiceText{ { text } });
- auto owned = AdminLog::OwnedItem(_delegate.get(), item);
- owned->overrideMedia(std::make_unique<MediaGeneric>(
- owned.get(),
- GenerateGiftMedia(owned.get(), _item.get(), _recipient, details),
- MediaGenericDescriptor{
- .maxWidth = st::chatIntroWidth,
- .service = true,
- }));
- _item = std::move(owned);
- if (width() >= st::msgMinWidth) {
- resizeTo(width());
- }
- update();
- }, lifetime());
- widthValue(
- ) | rpl::filter([=](int width) {
- return width >= st::msgMinWidth;
- }) | rpl::start_with_next([=](int width) {
- resizeTo(width);
- }, lifetime());
- _history->owner().itemResizeRequest(
- ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
- if (_item && item == _item->data() && width() >= st::msgMinWidth) {
- resizeTo(width());
- }
- }, lifetime());
- }
- void PreviewWrap::resizeTo(int width) {
- const auto height = _position.y()
- + _item->resizeGetHeight(width)
- + _position.y()
- + st::msgServiceMargin.top()
- + st::msgServiceGiftBoxTopSkip
- - st::msgServiceMargin.bottom();
- resize(width, height);
- }
- void PreviewWrap::paintEvent(QPaintEvent *e) {
- auto p = Painter(this);
- const auto clip = e->rect();
- if (!clip.isEmpty()) {
- p.setClipRect(clip);
- Window::SectionWidget::PaintBackground(
- p,
- _theme.get(),
- QSize(width(), window()->height()),
- clip);
- }
- auto context = _theme->preparePaintContext(
- _style.get(),
- rect(),
- e->rect(),
- !window()->isActiveWindow());
- p.translate(_position);
- _item->draw(p, context);
- }
- [[nodiscard]] rpl::producer<PremiumGiftsDescriptor> GiftsPremium(
- not_null<Main::Session*> session,
- not_null<PeerData*> peer) {
- struct Session {
- PremiumGiftsDescriptor last;
- };
- static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- auto i = Map.find(session);
- if (i == end(Map)) {
- i = Map.emplace(session, Session()).first;
- session->lifetime().add([=] { Map.remove(session); });
- }
- if (!i->second.last.list.empty()) {
- consumer.put_next_copy(i->second.last);
- }
- using namespace Api;
- const auto api = std::make_shared<PremiumGiftCodeOptions>(peer);
- api->request() | rpl::start_with_error_done([=](QString error) {
- consumer.put_next({});
- }, [=] {
- const auto &options = api->optionsForPeer();
- auto list = std::vector<GiftTypePremium>();
- list.reserve(options.size());
- auto minMonthsGift = GiftTypePremium();
- for (const auto &option : options) {
- if (option.currency != kCreditsCurrency) {
- list.push_back({
- .cost = option.cost,
- .currency = option.currency,
- .months = option.months,
- });
- if (!minMonthsGift.months
- || option.months < minMonthsGift.months) {
- minMonthsGift = list.back();
- }
- }
- }
- for (const auto &option : options) {
- if (option.currency == kCreditsCurrency) {
- const auto i = ranges::find(
- list,
- option.months,
- &GiftTypePremium::months);
- if (i != end(list)) {
- i->stars = option.cost;
- }
- }
- }
- for (auto &gift : list) {
- if (gift.months > minMonthsGift.months
- && gift.currency == minMonthsGift.currency) {
- const auto costPerMonth = gift.cost / (1. * gift.months);
- const auto maxCostPerMonth = minMonthsGift.cost
- / (1. * minMonthsGift.months);
- const auto costRatio = costPerMonth / maxCostPerMonth;
- const auto discount = 1. - costRatio;
- const auto discountPercent = 100 * discount;
- const auto value = int(base::SafeRound(discountPercent));
- if (value > 0 && value < 100) {
- gift.discountPercent = value;
- }
- }
- }
- ranges::sort(list, ranges::less(), &GiftTypePremium::months);
- auto &map = Map[session];
- if (map.last.list != list) {
- map.last = PremiumGiftsDescriptor{
- std::move(list),
- api,
- };
- consumer.put_next_copy(map.last);
- }
- }, lifetime);
- return lifetime;
- };
- }
- [[nodiscard]] rpl::producer<std::vector<GiftTypeStars>> GiftsStars(
- not_null<Main::Session*> session,
- not_null<PeerData*> peer) {
- struct Session {
- std::vector<GiftTypeStars> last;
- };
- static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- auto i = Map.find(session);
- if (i == end(Map)) {
- i = Map.emplace(session, Session()).first;
- session->lifetime().add([=] { Map.remove(session); });
- }
- if (!i->second.last.empty()) {
- consumer.put_next_copy(i->second.last);
- }
- using namespace Api;
- const auto api = lifetime.make_state<PremiumGiftCodeOptions>(peer);
- api->requestStarGifts(
- ) | rpl::start_with_error_done([=](QString error) {
- consumer.put_next({});
- }, [=] {
- auto list = std::vector<GiftTypeStars>();
- const auto &gifts = api->starGifts();
- list.reserve(gifts.size());
- for (auto &gift : gifts) {
- list.push_back({ .info = gift });
- }
- ranges::stable_sort(list, [](const auto &a, const auto &b) {
- return a.info.soldOut < b.info.soldOut;
- });
- auto &map = Map[session];
- if (map.last != list) {
- map.last = list;
- consumer.put_next_copy(list);
- }
- }, lifetime);
- return lifetime;
- };
- }
- [[nodiscard]] Text::String TabTextForPrice(
- not_null<Main::Session*> session,
- int price) {
- const auto simple = [](const QString &text) {
- return Text::String(st::semiboldTextStyle, text);
- };
- if (price == kPriceTabAll) {
- return simple(tr::lng_gift_stars_tabs_all(tr::now));
- } else if (price == kPriceTabLimited) {
- return simple(tr::lng_gift_stars_tabs_limited(tr::now));
- } else if (price == kPriceTabInStock) {
- return simple(tr::lng_gift_stars_tabs_in_stock(tr::now));
- }
- auto &manager = session->data().customEmojiManager();
- auto result = Text::String();
- result.setMarkedText(
- st::semiboldTextStyle,
- manager.creditsEmoji().append(QString::number(price)),
- kMarkupTextOptions,
- Core::TextContext({ .session = session }));
- return result;
- }
- struct GiftPriceTabs {
- rpl::producer<int> priceTab;
- object_ptr<RpWidget> widget;
- };
- [[nodiscard]] GiftPriceTabs MakeGiftsPriceTabs(
- not_null<Window::SessionController*> window,
- not_null<PeerData*> peer,
- rpl::producer<std::vector<GiftTypeStars>> gifts) {
- auto widget = object_ptr<RpWidget>((QWidget*)nullptr);
- const auto raw = widget.data();
- struct Button {
- QRect geometry;
- Text::String text;
- int price = 0;
- bool active = false;
- };
- struct State {
- rpl::variable<std::vector<int>> prices;
- rpl::variable<int> priceTab = kPriceTabAll;
- rpl::variable<int> fullWidth;
- std::vector<Button> buttons;
- int dragx = 0;
- int pressx = 0;
- float64 dragscroll = 0.;
- float64 scroll = 0.;
- int scrollMax = 0;
- int selected = -1;
- int pressed = -1;
- int active = -1;
- };
- const auto state = raw->lifetime().make_state<State>();
- const auto scroll = [=] {
- return QPoint(int(base::SafeRound(state->scroll)), 0);
- };
- state->prices = std::move(
- gifts
- ) | rpl::map([](const std::vector<GiftTypeStars> &gifts) {
- auto result = std::vector<int>();
- result.push_back(kPriceTabAll);
- auto special = 1;
- auto same = true;
- auto sameKey = 0;
- auto hasNonSoldOut = false;
- auto hasSoldOut = false;
- auto hasLimited = false;
- for (const auto &gift : gifts) {
- if (same) {
- const auto key = gift.info.stars
- * (gift.info.limitedCount ? -1 : 1);
- if (!sameKey) {
- sameKey = key;
- } else if (sameKey != key) {
- same = false;
- }
- }
- if (IsSoldOut(gift.info)) {
- hasSoldOut = true;
- } else {
- hasNonSoldOut = true;
- }
- if (gift.info.limitedCount) {
- hasLimited = true;
- }
- if (!ranges::contains(result, gift.info.stars)) {
- result.push_back(gift.info.stars);
- }
- }
- if (same) {
- return std::vector<int>();
- }
- if (hasSoldOut && hasNonSoldOut) {
- result.insert(begin(result) + (special++), kPriceTabInStock);
- }
- if (hasLimited) {
- result.insert(begin(result) + (special++), kPriceTabLimited);
- }
- ranges::sort(begin(result) + 1, end(result));
- return result;
- });
- const auto setSelected = [=](int index) {
- const auto was = (state->selected >= 0);
- const auto now = (index >= 0);
- state->selected = index;
- if (was != now) {
- raw->setCursor(now ? style::cur_pointer : style::cur_default);
- }
- };
- const auto setActive = [=](int index) {
- const auto was = state->active;
- if (was == index) {
- return;
- }
- if (was >= 0 && was < state->buttons.size()) {
- state->buttons[was].active = false;
- }
- state->active = index;
- state->buttons[index].active = true;
- raw->update();
- state->priceTab = state->buttons[index].price;
- };
- const auto session = &peer->session();
- state->prices.value(
- ) | rpl::start_with_next([=](const std::vector<int> &prices) {
- auto x = st::giftBoxTabsMargin.left();
- auto y = st::giftBoxTabsMargin.top();
- setSelected(-1);
- state->buttons.resize(prices.size());
- const auto padding = st::giftBoxTabPadding;
- auto currentPrice = state->priceTab.current();
- if (!ranges::contains(prices, currentPrice)) {
- currentPrice = kPriceTabAll;
- }
- state->active = -1;
- for (auto i = 0, count = int(prices.size()); i != count; ++i) {
- const auto price = prices[i];
- auto &button = state->buttons[i];
- if (button.text.isEmpty() || button.price != price) {
- button.price = price;
- button.text = TabTextForPrice(session, price);
- }
- button.active = (price == currentPrice);
- if (button.active) {
- state->active = i;
- }
- const auto width = button.text.maxWidth();
- const auto height = st::giftBoxTabStyle.font->height;
- const auto r = QRect(0, 0, width, height).marginsAdded(padding);
- button.geometry = QRect(QPoint(x, y), r.size());
- x += r.width() + st::giftBoxTabSkip;
- }
- state->fullWidth = x
- - st::giftBoxTabSkip
- + st::giftBoxTabsMargin.right();
- const auto height = state->buttons.empty()
- ? 0
- : (y
- + state->buttons.back().geometry.height()
- + st::giftBoxTabsMargin.bottom());
- raw->resize(raw->width(), height);
- raw->update();
- }, raw->lifetime());
- rpl::combine(
- raw->widthValue(),
- state->fullWidth.value()
- ) | rpl::start_with_next([=](int outer, int inner) {
- state->scrollMax = std::max(0, inner - outer);
- }, raw->lifetime());
- raw->setMouseTracking(true);
- raw->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
- const auto type = e->type();
- switch (type) {
- case QEvent::Leave: setSelected(-1); break;
- case QEvent::MouseMove: {
- const auto me = static_cast<QMouseEvent*>(e.get());
- const auto mousex = me->pos().x();
- const auto drag = QApplication::startDragDistance();
- if (state->dragx > 0) {
- state->scroll = std::clamp(
- state->dragscroll + state->dragx - mousex,
- 0.,
- state->scrollMax * 1.);
- raw->update();
- break;
- } else if (state->pressx > 0
- && std::abs(state->pressx - mousex) > drag) {
- state->dragx = state->pressx;
- state->dragscroll = state->scroll;
- }
- const auto position = me->pos() + scroll();
- for (auto i = 0, c = int(state->buttons.size()); i != c; ++i) {
- if (state->buttons[i].geometry.contains(position)) {
- setSelected(i);
- break;
- }
- }
- } break;
- case QEvent::Wheel: {
- const auto me = static_cast<QWheelEvent*>(e.get());
- state->scroll = std::clamp(
- state->scroll - ScrollDeltaF(me).x(),
- 0.,
- state->scrollMax * 1.);
- raw->update();
- } break;
- case QEvent::MouseButtonPress: {
- const auto me = static_cast<QMouseEvent*>(e.get());
- if (me->button() != Qt::LeftButton) {
- break;
- }
- state->pressed = state->selected;
- state->pressx = me->pos().x();
- } break;
- case QEvent::MouseButtonRelease: {
- const auto me = static_cast<QMouseEvent*>(e.get());
- if (me->button() != Qt::LeftButton) {
- break;
- }
- const auto dragx = std::exchange(state->dragx, 0);
- const auto pressed = std::exchange(state->pressed, -1);
- state->pressx = 0;
- if (!dragx && pressed >= 0 && state->selected == pressed) {
- setActive(pressed);
- }
- } break;
- }
- }, raw->lifetime());
- raw->paintRequest() | rpl::start_with_next([=] {
- auto p = QPainter(raw);
- auto hq = PainterHighQualityEnabler(p);
- const auto padding = st::giftBoxTabPadding;
- const auto shift = -scroll();
- for (const auto &button : state->buttons) {
- const auto geometry = button.geometry.translated(shift);
- if (button.active) {
- p.setBrush(st::giftBoxTabBgActive);
- p.setPen(Qt::NoPen);
- const auto radius = geometry.height() / 2.;
- p.drawRoundedRect(geometry, radius, radius);
- p.setPen(st::giftBoxTabFgActive);
- } else {
- p.setPen(st::giftBoxTabFg);
- }
- button.text.draw(p, {
- .position = geometry.marginsRemoved(padding).topLeft(),
- .availableWidth = button.text.maxWidth(),
- });
- }
- {
- const auto &icon = st::defaultEmojiSuggestions;
- const auto w = icon.fadeRight.width();
- const auto &c = st::boxDividerBg->c;
- const auto r = QRect(0, 0, w, raw->height());
- const auto s = std::abs(float64(shift.x()));
- constexpr auto kF = 0.5;
- const auto opacityRight = (state->scrollMax - s)
- / (icon.fadeRight.width() * kF);
- p.setOpacity(std::clamp(std::abs(opacityRight), 0., 1.));
- icon.fadeRight.fill(p, r.translated(raw->width() - w, 0), c);
- const auto opacityLeft = s / (icon.fadeLeft.width() * kF);
- p.setOpacity(std::clamp(std::abs(opacityLeft), 0., 1.));
- icon.fadeLeft.fill(p, r, c);
- }
- }, raw->lifetime());
- return {
- .priceTab = state->priceTab.value(),
- .widget = std::move(widget),
- };
- }
- [[nodiscard]] int StarGiftMessageLimit(not_null<Main::Session*> session) {
- return session->appConfig().get<int>(
- u"stargifts_message_length_max"_q,
- 255);
- }
- [[nodiscard]] not_null<InputField*> AddPartInput(
- not_null<Window::SessionController*> controller,
- not_null<VerticalLayout*> container,
- not_null<QWidget*> outer,
- rpl::producer<QString> placeholder,
- QString current,
- int limit) {
- const auto field = container->add(
- object_ptr<InputField>(
- container,
- st::giftBoxTextField,
- InputField::Mode::NoNewlines,
- std::move(placeholder),
- current),
- st::giftBoxTextPadding);
- field->setMaxLength(limit);
- AddLengthLimitLabel(field, limit, std::nullopt, st::giftBoxLimitTop);
- const auto toggle = CreateChild<EmojiButton>(
- container,
- st::defaultComposeFiles.emoji);
- toggle->show();
- field->geometryValue() | rpl::start_with_next([=](QRect r) {
- toggle->move(
- r.x() + r.width() - toggle->width(),
- r.y() - st::giftBoxEmojiToggleTop);
- }, toggle->lifetime());
- using namespace ChatHelpers;
- const auto panel = field->lifetime().make_state<TabbedPanel>(
- outer,
- controller,
- object_ptr<TabbedSelector>(
- nullptr,
- controller->uiShow(),
- Window::GifPauseReason::Layer,
- TabbedSelector::Mode::EmojiOnly));
- panel->setDesiredHeightValues(
- 1.,
- st::emojiPanMinHeight / 2,
- st::emojiPanMinHeight);
- panel->hide();
- panel->selector()->setAllowEmojiWithoutPremium(true);
- panel->selector()->emojiChosen(
- ) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
- InsertEmojiAtCursor(field->textCursor(), data.emoji);
- }, field->lifetime());
- panel->selector()->customEmojiChosen(
- ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
- Data::InsertCustomEmoji(field, data.document);
- }, field->lifetime());
- const auto updateEmojiPanelGeometry = [=] {
- const auto parent = panel->parentWidget();
- const auto global = toggle->mapToGlobal({ 0, 0 });
- const auto local = parent->mapFromGlobal(global);
- panel->moveBottomRight(
- local.y(),
- local.x() + toggle->width() * 3);
- };
- const auto filterCallback = [=](not_null<QEvent*> event) {
- const auto type = event->type();
- if (type == QEvent::Move || type == QEvent::Resize) {
- // updateEmojiPanelGeometry uses not only container geometry, but
- // also container children geometries that will be updated later.
- crl::on_main(field, updateEmojiPanelGeometry);
- }
- return base::EventFilterResult::Continue;
- };
- for (auto widget = (QWidget*)field, end = (QWidget*)outer->parentWidget()
- ; widget && widget != end
- ; widget = widget->parentWidget()) {
- base::install_event_filter(field, widget, filterCallback);
- }
- toggle->installEventFilter(panel);
- toggle->addClickHandler([=] {
- panel->toggleAnimated();
- });
- return field;
- }
- void SendGift(
- not_null<Window::SessionController*> window,
- not_null<PeerData*> peer,
- std::shared_ptr<Api::PremiumGiftCodeOptions> api,
- const GiftDetails &details,
- Fn<void(Payments::CheckoutResult)> done) {
- const auto processNonPanelPaymentFormFactory
- = Payments::ProcessNonPanelPaymentFormFactory(window, done);
- v::match(details.descriptor, [&](const GiftTypePremium &gift) {
- if (details.byStars && gift.stars) {
- auto invoice = Payments::InvoicePremiumGiftCode{
- .purpose = Payments::InvoicePremiumGiftCodeUsers{
- .users = { peer->asUser() },
- .message = details.text,
- },
- .currency = Ui::kCreditsCurrency,
- .randomId = details.randomId,
- .amount = uint64(gift.stars),
- .storeQuantity = 1,
- .users = 1,
- .months = gift.months,
- };
- Payments::CheckoutProcess::Start(
- std::move(invoice),
- done,
- processNonPanelPaymentFormFactory);
- } else {
- auto invoice = api->invoice(1, gift.months);
- invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{
- .users = { peer->asUser() },
- .message = details.text,
- };
- Payments::CheckoutProcess::Start(std::move(invoice), done);
- }
- }, [&](const GiftTypeStars &gift) {
- Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{
- .giftId = gift.info.id,
- .randomId = details.randomId,
- .message = details.text,
- .recipient = peer,
- .limitedCount = gift.info.limitedCount,
- .anonymous = details.anonymous,
- .upgraded = details.upgraded,
- }, done, processNonPanelPaymentFormFactory);
- });
- }
- [[nodiscard]] std::shared_ptr<Data::UniqueGift> FindUniqueGift(
- not_null<Main::Session*> session,
- const MTPUpdates &updates) {
- auto result = std::shared_ptr<Data::UniqueGift>();
- const auto checkAction = [&](const MTPMessageAction &action) {
- action.match([&](const MTPDmessageActionStarGiftUnique &data) {
- if (const auto gift = Api::FromTL(session, data.vgift())) {
- result = gift->unique;
- }
- }, [](const auto &) {});
- };
- updates.match([&](const MTPDupdates &data) {
- for (const auto &update : data.vupdates().v) {
- update.match([&](const MTPDupdateNewMessage &data) {
- data.vmessage().match([&](const MTPDmessageService &data) {
- checkAction(data.vaction());
- }, [](const auto &) {});
- }, [](const auto &) {});
- }
- }, [](const auto &) {});
- return result;
- }
- void ShowGiftUpgradedToast(
- base::weak_ptr<Window::SessionController> weak,
- not_null<Main::Session*> session,
- const MTPUpdates &result) {
- const auto gift = FindUniqueGift(session, result);
- if (const auto strong = gift ? weak.get() : nullptr) {
- strong->showToast({
- .title = tr::lng_gift_upgraded_title(tr::now),
- .text = tr::lng_gift_upgraded_about(
- tr::now,
- lt_name,
- Text::Bold(Data::UniqueGiftName(*gift)),
- Ui::Text::WithEntities),
- .duration = kUpgradeDoneToastDuration,
- });
- }
- }
- void SendStarsFormRequest(
- not_null<Window::SessionController*> controller,
- Settings::SmallBalanceResult result,
- uint64 formId,
- MTPInputInvoice invoice,
- Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done) {
- using BalanceResult = Settings::SmallBalanceResult;
- const auto session = &controller->session();
- if (result == BalanceResult::Success
- || result == BalanceResult::Already) {
- const auto weak = base::make_weak(controller);
- session->api().request(MTPpayments_SendStarsForm(
- MTP_long(formId),
- invoice
- )).done([=](const MTPpayments_PaymentResult &result) {
- result.match([&](const MTPDpayments_paymentResult &data) {
- session->api().applyUpdates(data.vupdates());
- done(Payments::CheckoutResult::Paid, &data.vupdates());
- }, [&](const MTPDpayments_paymentVerificationNeeded &data) {
- done(Payments::CheckoutResult::Failed, nullptr);
- });
- }).fail([=](const MTP::Error &error) {
- if (const auto strong = weak.get()) {
- strong->showToast(error.type());
- }
- done(Payments::CheckoutResult::Failed, nullptr);
- }).send();
- } else if (result == BalanceResult::Cancelled) {
- done(Payments::CheckoutResult::Cancelled, nullptr);
- } else {
- done(Payments::CheckoutResult::Failed, nullptr);
- }
- }
- void UpgradeGift(
- not_null<Window::SessionController*> window,
- Data::SavedStarGiftId savedId,
- bool keepDetails,
- int stars,
- Fn<void(Payments::CheckoutResult)> done) {
- const auto session = &window->session();
- const auto weak = base::make_weak(window);
- auto formDone = [=](
- Payments::CheckoutResult result,
- const MTPUpdates *updates) {
- if (result == Payments::CheckoutResult::Paid && updates) {
- if (const auto strong = weak.get()) {
- ShowGiftUpgradedToast(strong, session, *updates);
- }
- }
- done(result);
- };
- if (stars <= 0) {
- using Flag = MTPpayments_UpgradeStarGift::Flag;
- session->api().request(MTPpayments_UpgradeStarGift(
- MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
- Api::InputSavedStarGiftId(savedId)
- )).done([=](const MTPUpdates &result) {
- session->api().applyUpdates(result);
- formDone(Payments::CheckoutResult::Paid, &result);
- }).fail([=](const MTP::Error &error) {
- if (const auto strong = weak.get()) {
- strong->showToast(error.type());
- }
- formDone(Payments::CheckoutResult::Failed, nullptr);
- }).send();
- return;
- }
- using Flag = MTPDinputInvoiceStarGiftUpgrade::Flag;
- RequestStarsFormAndSubmit(
- window,
- MTP_inputInvoiceStarGiftUpgrade(
- MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
- Api::InputSavedStarGiftId(savedId)),
- std::move(formDone));
- }
- void SoldOutBox(
- not_null<GenericBox*> box,
- not_null<Window::SessionController*> window,
- const GiftTypeStars &gift) {
- Settings::ReceiptCreditsBox(
- box,
- window,
- Data::CreditsHistoryEntry{
- .firstSaleDate = base::unixtime::parse(gift.info.firstSaleDate),
- .lastSaleDate = base::unixtime::parse(gift.info.lastSaleDate),
- .credits = StarsAmount(gift.info.stars),
- .bareGiftStickerId = gift.info.document->id,
- .peerType = Data::CreditsHistoryEntry::PeerType::Peer,
- .limitedCount = gift.info.limitedCount,
- .limitedLeft = gift.info.limitedLeft,
- .soldOutInfo = true,
- .gift = true,
- },
- Data::SubscriptionEntry());
- }
- void AddUpgradeButton(
- not_null<Ui::VerticalLayout*> container,
- not_null<Main::Session*> session,
- int cost,
- not_null<PeerData*> peer,
- Fn<void(bool)> toggled,
- Fn<void()> preview) {
- const auto button = container->add(
- object_ptr<SettingsButton>(
- container,
- rpl::single(QString()),
- st::settingsButtonNoIcon));
- button->toggleOn(rpl::single(false))->toggledValue(
- ) | rpl::start_with_next(toggled, button->lifetime());
- auto star = session->data().customEmojiManager().creditsEmoji();
- const auto label = Ui::CreateChild<Ui::FlatLabel>(
- button,
- tr::lng_gift_send_unique(
- lt_price,
- rpl::single(star.append(' '
- + Lang::FormatStarsAmountDecimal(
- StarsAmount{ cost }))),
- Text::WithEntities),
- st::boxLabel,
- st::defaultPopupMenu,
- Core::TextContext({ .session = session }));
- label->show();
- label->setAttribute(Qt::WA_TransparentForMouseEvents);
- button->widthValue() | rpl::start_with_next([=](int outer) {
- const auto padding = st::settingsButtonNoIcon.padding;
- const auto inner = outer
- - padding.left()
- - padding.right()
- - st::settingsButtonNoIcon.toggleSkip
- - 2 * st::settingsButtonNoIcon.toggle.border
- - 2 * st::settingsButtonNoIcon.toggle.diameter
- - 2 * st::settingsButtonNoIcon.toggle.width;
- label->resizeToWidth(inner);
- label->moveToLeft(padding.left(), padding.top(), outer);
- }, label->lifetime());
- AddSkip(container);
- const auto about = AddDividerText(
- container,
- (peer->isBroadcast()
- ? tr::lng_gift_send_unique_about_channel(
- lt_name,
- rpl::single(TextWithEntities{ peer->name() }),
- lt_link,
- tr::lng_gift_send_unique_link() | Text::ToLink(),
- Text::WithEntities)
- : tr::lng_gift_send_unique_about(
- lt_user,
- rpl::single(TextWithEntities{ peer->shortName() }),
- lt_link,
- tr::lng_gift_send_unique_link() | Text::ToLink(),
- Text::WithEntities)));
- about->setClickHandlerFilter([=](const auto &...) {
- preview();
- return false;
- });
- }
- void AddSoldLeftSlider(
- not_null<RoundButton*> button,
- const GiftTypeStars &gift) {
- const auto still = gift.info.limitedLeft;
- const auto total = gift.info.limitedCount;
- const auto slider = CreateChild<RpWidget>(button->parentWidget());
- struct State {
- Text::String still;
- Text::String sold;
- int height = 0;
- };
- const auto state = slider->lifetime().make_state<State>();
- const auto sold = total - still;
- state->still.setText(
- st::semiboldTextStyle,
- tr::lng_gift_send_limited_left(tr::now, lt_count_decimal, still));
- state->sold.setText(
- st::semiboldTextStyle,
- tr::lng_gift_send_limited_sold(tr::now, lt_count_decimal, sold));
- state->height = st::giftLimitedPadding.top()
- + st::semiboldFont->height
- + st::giftLimitedPadding.bottom();
- button->geometryValue() | rpl::start_with_next([=](QRect geometry) {
- const auto space = st::giftLimitedBox.buttonPadding.top();
- const auto skip = (space - state->height) / 2;
- slider->setGeometry(
- geometry.x(),
- geometry.y() - skip - state->height,
- geometry.width(),
- state->height);
- }, slider->lifetime());
- slider->paintRequest() | rpl::start_with_next([=] {
- const auto &padding = st::giftLimitedPadding;
- const auto left = (padding.left() * 2) + state->still.maxWidth();
- const auto right = (padding.right() * 2) + state->sold.maxWidth();
- const auto space = slider->width() - left - right;
- if (space <= 0) {
- return;
- }
- const auto edge = left + ((space * still) / total);
- const auto radius = st::buttonRadius;
- auto p = QPainter(slider);
- auto hq = PainterHighQualityEnabler(p);
- p.setPen(Qt::NoPen);
- p.setBrush(st::windowBgOver);
- p.drawRoundedRect(
- edge - (radius * 3),
- 0,
- slider->width() - (edge - (radius * 3)),
- state->height,
- radius,
- radius);
- p.setBrush(st::windowBgActive);
- p.drawRoundedRect(0, 0, edge, state->height, radius, radius);
- p.setPen(st::windowFgActive);
- state->still.draw(p, {
- .position = { padding.left(), padding.top() },
- .availableWidth = left,
- });
- p.setPen(st::windowSubTextFg);
- state->sold.draw(p, {
- .position = { left + space + padding.right(), padding.top() },
- .availableWidth = right,
- });
- }, slider->lifetime());
- }
- void SendGiftBox(
- not_null<GenericBox*> box,
- not_null<Window::SessionController*> window,
- not_null<PeerData*> peer,
- std::shared_ptr<Api::PremiumGiftCodeOptions> api,
- const GiftDescriptor &descriptor) {
- const auto stars = std::get_if<GiftTypeStars>(&descriptor);
- const auto limited = stars
- && (stars->info.limitedCount > stars->info.limitedLeft)
- && (stars->info.limitedLeft > 0);
- box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
- box->setWidth(st::boxWideWidth);
- box->setTitle(tr::lng_gift_send_title());
- box->addTopButton(st::boxTitleClose, [=] {
- box->closeBox();
- });
- const auto session = &window->session();
- struct State {
- rpl::variable<GiftDetails> details;
- rpl::variable<bool> messageAllowed;
- std::shared_ptr<Data::DocumentMedia> media;
- bool submitting = false;
- };
- const auto state = box->lifetime().make_state<State>();
- state->details = GiftDetails{
- .descriptor = descriptor,
- .randomId = base::RandomValue<uint64>(),
- };
- peer->updateFull();
- state->messageAllowed = peer->session().changes().peerFlagsValue(
- peer,
- Data::PeerUpdate::Flag::StarsPerMessage
- ) | rpl::map([=] {
- return peer->starsPerMessageChecked() == 0;
- });
- auto cost = state->details.value(
- ) | rpl::map([session](const GiftDetails &details) {
- return v::match(details.descriptor, [&](const GiftTypePremium &data) {
- const auto stars = (details.byStars && data.stars)
- ? data.stars
- : (data.currency == kCreditsCurrency)
- ? data.cost
- : 0;
- if (stars) {
- return CreditsEmojiSmall(session).append(
- Lang::FormatCountDecimal(std::abs(stars)));
- }
- return TextWithEntities{
- FillAmountAndCurrency(data.cost, data.currency),
- };
- }, [&](const GiftTypeStars &data) {
- const auto amount = std::abs(data.info.stars)
- + (details.upgraded ? data.info.starsToUpgrade : 0);
- return CreditsEmojiSmall(session).append(
- Lang::FormatCountDecimal(amount));
- });
- });
- const auto document = LookupGiftSticker(session, descriptor);
- if ((state->media = document ? document->createMediaView() : nullptr)) {
- state->media->checkStickerLarge();
- }
- const auto container = box->verticalLayout();
- container->add(object_ptr<PreviewWrap>(
- container,
- peer,
- state->details.value()));
- const auto messageWrap = container->add(
- object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
- container,
- object_ptr<Ui::VerticalLayout>(container)));
- messageWrap->toggleOn(state->messageAllowed.value());
- messageWrap->finishAnimating();
- const auto messageInner = messageWrap->entity();
- const auto limit = StarGiftMessageLimit(session);
- const auto text = AddPartInput(
- window,
- messageInner,
- box->getDelegate()->outerContainer(),
- tr::lng_gift_send_message(),
- QString(),
- limit);
- text->changes() | rpl::start_with_next([=] {
- auto now = state->details.current();
- auto textWithTags = text->getTextWithAppliedMarkdown();
- now.text = TextWithEntities{
- std::move(textWithTags.text),
- TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
- };
- state->details = std::move(now);
- }, text->lifetime());
- box->setFocusCallback([=] {
- text->setFocusFast();
- });
- const auto allow = [=](not_null<DocumentData*> emoji) {
- return true;
- };
- InitMessageFieldHandlers({
- .session = session,
- .show = window->uiShow(),
- .field = text,
- .customEmojiPaused = [=] {
- using namespace Window;
- return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
- },
- .allowPremiumEmoji = allow,
- .allowMarkdownTags = {
- InputField::kTagBold,
- InputField::kTagItalic,
- InputField::kTagUnderline,
- InputField::kTagStrikeOut,
- InputField::kTagSpoiler,
- }
- });
- Emoji::SuggestionsController::Init(
- box->getDelegate()->outerContainer(),
- text,
- session,
- { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
- if (stars) {
- const auto cost = stars->info.starsToUpgrade;
- if (cost > 0 && !peer->isSelf()) {
- const auto id = stars->info.id;
- const auto showing = std::make_shared<bool>();
- AddDivider(container);
- AddSkip(container);
- AddUpgradeButton(container, session, cost, peer, [=](bool on) {
- auto now = state->details.current();
- now.upgraded = on;
- state->details = std::move(now);
- }, [=] {
- if (*showing) {
- return;
- }
- *showing = true;
- ShowStarGiftUpgradeBox({
- .controller = window,
- .stargiftId = id,
- .ready = [=](bool) { *showing = false; },
- .peer = peer,
- .cost = int(cost),
- });
- });
- } else {
- AddDivider(container);
- }
- AddSkip(container);
- container->add(
- object_ptr<SettingsButton>(
- container,
- tr::lng_gift_send_anonymous(),
- st::settingsButtonNoIcon)
- )->toggleOn(rpl::single(peer->isSelf()))->toggledValue(
- ) | rpl::start_with_next([=](bool toggled) {
- auto now = state->details.current();
- now.anonymous = toggled;
- state->details = std::move(now);
- }, container->lifetime());
- AddSkip(container);
- }
- v::match(descriptor, [&](const GiftTypePremium &data) {
- AddDividerText(messageInner, tr::lng_gift_send_premium_about(
- lt_user,
- rpl::single(peer->shortName())));
- if (const auto byStars = data.stars) {
- const auto star = Ui::Text::IconEmoji(&st::starIconEmojiColored);
- AddSkip(container);
- container->add(
- object_ptr<SettingsButton>(
- container,
- tr::lng_gift_send_pay_with_stars(
- lt_amount,
- rpl::single(base::duplicate(star).append(Lang::FormatCountDecimal(byStars))),
- Ui::Text::WithEntities),
- st::settingsButtonNoIcon)
- )->toggleOn(rpl::single(false))->toggledValue(
- ) | rpl::start_with_next([=](bool toggled) {
- auto now = state->details.current();
- now.byStars = toggled;
- state->details = std::move(now);
- }, container->lifetime());
- AddSkip(container);
- const auto balance = AddDividerText(
- container,
- tr::lng_gift_send_stars_balance(
- lt_amount,
- peer->session().credits().balanceValue(
- ) | rpl::map([=](StarsAmount amount) {
- return base::duplicate(star).append(
- Lang::FormatStarsAmountDecimal(amount));
- }),
- lt_link,
- tr::lng_gift_send_stars_balance_link(
- ) | Ui::Text::ToLink(),
- Ui::Text::WithEntities));
- struct State {
- Settings::BuyStarsHandler buyStars;
- rpl::variable<bool> loading;
- };
- const auto state = balance->lifetime().make_state<State>();
- state->loading = state->buyStars.loadingValue();
- balance->setClickHandlerFilter([=](const auto &...) {
- if (!state->loading.current()) {
- state->buyStars.handler(window->uiShow())();
- }
- return false;
- });
- }
- }, [&](const GiftTypeStars &) {
- AddDividerText(container, peer->isSelf()
- ? tr::lng_gift_send_anonymous_self()
- : peer->isBroadcast()
- ? tr::lng_gift_send_anonymous_about_channel()
- : rpl::conditional(
- state->messageAllowed.value(),
- tr::lng_gift_send_anonymous_about(
- lt_user,
- rpl::single(peer->shortName()),
- lt_recipient,
- rpl::single(peer->shortName())),
- tr::lng_gift_send_anonymous_about_paid(
- lt_user,
- rpl::single(peer->shortName()),
- lt_recipient,
- rpl::single(peer->shortName()))));
- });
- const auto buttonWidth = st::boxWideWidth
- - st::giftBox.buttonPadding.left()
- - st::giftBox.buttonPadding.right();
- const auto button = box->addButton(rpl::single(QString()), [=] {
- if (state->submitting) {
- return;
- }
- state->submitting = true;
- auto details = state->details.current();
- if (!state->messageAllowed.current()) {
- details.text = {};
- }
- const auto weak = MakeWeak(box);
- const auto done = [=](Payments::CheckoutResult result) {
- if (result == Payments::CheckoutResult::Paid) {
- if (details.byStars
- || v::is<GiftTypeStars>(details.descriptor)) {
- window->session().credits().load(true);
- }
- const auto copy = state->media;
- window->showPeerHistory(peer);
- ShowSentToast(window, details.descriptor, details);
- }
- if (const auto strong = weak.data()) {
- box->closeBox();
- }
- };
- SendGift(window, peer, api, details, done);
- });
- if (limited) {
- AddSoldLeftSlider(button, *stars);
- }
- SetButtonMarkedLabel(
- button,
- (peer->isSelf()
- ? tr::lng_gift_send_button_self
- : tr::lng_gift_send_button)(
- lt_cost,
- std::move(cost),
- Text::WithEntities),
- session,
- st::creditsBoxButtonLabel,
- &st::giftBox.button.textFg);
- button->resizeToWidth(buttonWidth);
- button->widthValue() | rpl::start_with_next([=](int width) {
- if (width != buttonWidth) {
- button->resizeToWidth(buttonWidth);
- }
- }, button->lifetime());
- }
- [[nodiscard]] object_ptr<RpWidget> MakeGiftsList(
- not_null<Window::SessionController*> window,
- not_null<PeerData*> peer,
- rpl::producer<GiftsDescriptor> gifts) {
- auto result = object_ptr<RpWidget>((QWidget*)nullptr);
- const auto raw = result.data();
- struct State {
- Delegate delegate;
- std::vector<std::unique_ptr<GiftButton>> buttons;
- bool sending = false;
- };
- const auto state = raw->lifetime().make_state<State>(State{
- .delegate = Delegate(&window->session(), GiftButtonMode::Full),
- });
- const auto single = state->delegate.buttonSize();
- const auto shadow = st::defaultDropdownMenu.wrap.shadow;
- const auto extend = shadow.extend;
- auto &packs = window->session().giftBoxStickersPacks();
- packs.updated() | rpl::start_with_next([=] {
- for (const auto &button : state->buttons) {
- button->update();
- }
- }, raw->lifetime());
- std::move(
- gifts
- ) | rpl::start_with_next([=](const GiftsDescriptor &gifts) {
- const auto width = st::boxWideWidth;
- const auto padding = st::giftBoxPadding;
- const auto available = width - padding.left() - padding.right();
- const auto perRow = available / single.width();
- const auto count = int(gifts.list.size());
- auto order = ranges::views::ints
- | ranges::views::take(count)
- | ranges::to_vector;
- if (SortForBirthday(peer)) {
- ranges::stable_partition(order, [&](int i) {
- const auto &gift = gifts.list[i];
- const auto stars = std::get_if<GiftTypeStars>(&gift);
- return stars && stars->info.birthday;
- });
- }
- auto x = padding.left();
- auto y = padding.top();
- state->buttons.resize(count);
- for (auto &button : state->buttons) {
- if (!button) {
- button = std::make_unique<GiftButton>(raw, &state->delegate);
- button->show();
- }
- }
- const auto api = gifts.api;
- for (auto i = 0; i != count; ++i) {
- const auto button = state->buttons[i].get();
- const auto &descriptor = gifts.list[order[i]];
- button->setDescriptor(descriptor, GiftButton::Mode::Full);
- const auto last = !((i + 1) % perRow);
- if (last) {
- x = padding.left() + available - single.width();
- }
- button->setGeometry(QRect(QPoint(x, y), single), extend);
- if (last) {
- x = padding.left();
- y += single.height() + st::giftBoxGiftSkip.y();
- } else {
- x += single.width() + st::giftBoxGiftSkip.x();
- }
- button->setClickedCallback([=] {
- const auto star = std::get_if<GiftTypeStars>(&descriptor);
- if (star && IsSoldOut(star->info)) {
- window->show(Box(SoldOutBox, window, *star));
- } else {
- window->show(
- Box(SendGiftBox, window, peer, api, descriptor));
- }
- });
- }
- if (count % perRow) {
- y += padding.bottom() + single.height();
- } else {
- y += padding.bottom() - st::giftBoxGiftSkip.y();
- }
- raw->resize(raw->width(), count ? y : 0);
- }, raw->lifetime());
- return result;
- }
- void FillBg(not_null<RpWidget*> box) {
- box->paintRequest() | rpl::start_with_next([=] {
- auto p = QPainter(box);
- auto hq = PainterHighQualityEnabler(p);
- const auto radius = st::boxRadius;
- p.setPen(Qt::NoPen);
- p.setBrush(st::boxDividerBg);
- p.drawRoundedRect(
- box->rect().marginsAdded({ 0, 0, 0, 2 * radius }),
- radius,
- radius);
- }, box->lifetime());
- }
- struct AddBlockArgs {
- rpl::producer<QString> subtitle;
- rpl::producer<TextWithEntities> about;
- Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)> aboutFilter;
- object_ptr<RpWidget> content;
- };
- void AddBlock(
- not_null<VerticalLayout*> content,
- not_null<Window::SessionController*> window,
- AddBlockArgs &&args) {
- content->add(
- object_ptr<FlatLabel>(
- content,
- std::move(args.subtitle),
- st::giftBoxSubtitle),
- st::giftBoxSubtitleMargin);
- const auto about = content->add(
- object_ptr<FlatLabel>(
- content,
- std::move(args.about),
- st::giftBoxAbout),
- st::giftBoxAboutMargin);
- about->setClickHandlerFilter(std::move(args.aboutFilter));
- content->add(std::move(args.content));
- }
- [[nodiscard]] object_ptr<RpWidget> MakePremiumGifts(
- not_null<Window::SessionController*> window,
- not_null<PeerData*> peer) {
- struct State {
- rpl::variable<PremiumGiftsDescriptor> gifts;
- };
- auto state = std::make_unique<State>();
- state->gifts = GiftsPremium(&window->session(), peer);
- auto result = MakeGiftsList(window, peer, state->gifts.value(
- ) | rpl::map([=](const PremiumGiftsDescriptor &gifts) {
- return GiftsDescriptor{
- gifts.list | ranges::to<std::vector<GiftDescriptor>>,
- gifts.api,
- };
- }));
- result->lifetime().add([state = std::move(state)] {});
- return result;
- }
- [[nodiscard]] object_ptr<RpWidget> MakeStarsGifts(
- not_null<Window::SessionController*> window,
- not_null<PeerData*> peer) {
- auto result = object_ptr<VerticalLayout>((QWidget*)nullptr);
- struct State {
- rpl::variable<std::vector<GiftTypeStars>> gifts;
- rpl::variable<int> priceTab = kPriceTabAll;
- };
- const auto state = result->lifetime().make_state<State>();
- state->gifts = GiftsStars(&window->session(), peer);
- auto tabs = MakeGiftsPriceTabs(window, peer, state->gifts.value());
- state->priceTab = std::move(tabs.priceTab);
- result->add(std::move(tabs.widget));
- result->add(MakeGiftsList(window, peer, rpl::combine(
- state->gifts.value(),
- state->priceTab.value()
- ) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) {
- gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) {
- return (price == kPriceTabLimited)
- ? (!gift.info.limitedCount)
- : (price == kPriceTabInStock)
- ? IsSoldOut(gift.info)
- : (price && gift.info.stars != price);
- }), end(gifts));
- return GiftsDescriptor{
- gifts | ranges::to<std::vector<GiftDescriptor>>(),
- };
- })));
- return result;
- }
- void GiftBox(
- not_null<GenericBox*> box,
- not_null<Window::SessionController*> window,
- not_null<PeerData*> peer) {
- box->setWidth(st::boxWideWidth);
- box->setStyle(st::creditsGiftBox);
- box->setNoContentMargin(true);
- box->setCustomCornersFilling(RectPart::FullTop);
- box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
- window->session().credits().load();
- FillBg(box);
- const auto &stUser = st::premiumGiftsUserpicButton;
- const auto content = box->verticalLayout();
- AddSkip(content, st::defaultVerticalListSkip * 5);
- content->add(
- object_ptr<CenterWrap<>>(
- content,
- object_ptr<UserpicButton>(content, peer, stUser))
- )->setAttribute(Qt::WA_TransparentForMouseEvents);
- AddSkip(content);
- AddSkip(content);
- Settings::AddMiniStars(
- content,
- CreateChild<RpWidget>(content),
- stUser.photoSize,
- box->width(),
- 2.);
- AddSkip(content);
- AddSkip(box->verticalLayout());
- const auto starsClickHandlerFilter = [=](const auto &...) {
- window->showSettings(Settings::CreditsId());
- return false;
- };
- if (peer->isUser() && !peer->isSelf()) {
- const auto premiumClickHandlerFilter = [=](const auto &...) {
- Settings::ShowPremium(window, u"gift_send"_q);
- return false;
- };
- AddBlock(content, window, {
- .subtitle = tr::lng_gift_premium_subtitle(),
- .about = tr::lng_gift_premium_about(
- lt_name,
- rpl::single(Text::Bold(peer->shortName())),
- lt_features,
- tr::lng_gift_premium_features() | Text::ToLink(),
- Text::WithEntities),
- .aboutFilter = premiumClickHandlerFilter,
- .content = MakePremiumGifts(window, peer),
- });
- }
- AddBlock(content, window, {
- .subtitle = (peer->isSelf()
- ? tr::lng_gift_self_title()
- : peer->isBroadcast()
- ? tr::lng_gift_channel_title()
- : tr::lng_gift_stars_subtitle()),
- .about = (peer->isSelf()
- ? tr::lng_gift_self_about(Text::WithEntities)
- : peer->isBroadcast()
- ? tr::lng_gift_channel_about(
- lt_name,
- rpl::single(Text::Bold(peer->name())),
- Text::WithEntities)
- : tr::lng_gift_stars_about(
- lt_name,
- rpl::single(Text::Bold(peer->shortName())),
- lt_link,
- tr::lng_gift_stars_link() | Text::ToLink(),
- Text::WithEntities)),
- .aboutFilter = starsClickHandlerFilter,
- .content = MakeStarsGifts(window, peer),
- });
- }
- struct SelfOption {
- object_ptr<Ui::RpWidget> content = { nullptr };
- Fn<bool(int, int, int)> overrideKey;
- Fn<void()> activate;
- };
- class Controller final : public ContactsBoxController {
- public:
- Controller(
- not_null<Main::Session*> session,
- Fn<void(not_null<PeerData*>)> choose);
- void noSearchSubmit();
- bool overrideKeyboardNavigation(
- int direction,
- int fromIndex,
- int toIndex) override;
- private:
- std::unique_ptr<PeerListRow> createRow(
- not_null<UserData*> user) override;
- void prepareViewHook() override;
- void rowClicked(not_null<PeerListRow*> row) override;
- const Fn<void(not_null<PeerData*>)> _choose;
- SelfOption _selfOption;
- };
- [[nodiscard]] SelfOption MakeSelfOption(
- not_null<Main::Session*> session,
- Fn<void()> activate) {
- class SelfController final : public PeerListController {
- public:
- SelfController(
- not_null<Main::Session*> session,
- Fn<void()> activate)
- : _session(session)
- , _activate(std::move(activate)) {
- }
- void prepare() override {
- auto row = std::make_unique<PeerListRow>(_session->user());
- row->setCustomStatus(tr::lng_gift_self_status(tr::now));
- delegate()->peerListAppendRow(std::move(row));
- delegate()->peerListRefreshRows();
- }
- void loadMoreRows() override {
- }
- void rowClicked(not_null<PeerListRow*> row) override {
- _activate();
- }
- Main::Session &session() const override {
- return *_session;
- }
- private:
- const not_null<Main::Session*> _session;
- Fn<void()> _activate;
- };
- auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
- const auto container = result.data();
- Ui::AddSkip(container);
- const auto delegate = container->lifetime().make_state<
- PeerListContentDelegateSimple
- >();
- const auto controller = container->lifetime().make_state<
- SelfController
- >(session, activate);
- controller->setStyleOverrides(&st::peerListSingleRow);
- const auto content = container->add(object_ptr<PeerListContent>(
- container,
- controller));
- delegate->setContent(content);
- controller->setDelegate(delegate);
- Ui::AddSkip(container);
- container->add(CreatePeerListSectionSubtitle(
- container,
- tr::lng_contacts_header()));
- const auto overrideKey = [=](int direction, int from, int to) {
- if (!content->isVisible()) {
- return false;
- } else if (direction > 0 && from < 0 && to >= 0) {
- if (content->hasSelection()) {
- const auto was = content->selectedIndex();
- const auto now = content->selectSkip(1).reallyMovedTo;
- if (was != now) {
- return true;
- }
- content->clearSelection();
- } else {
- content->selectSkip(1);
- return true;
- }
- } else if (direction < 0 && to < 0) {
- if (!content->hasSelection()) {
- content->selectLast();
- } else if (from >= 0 || content->hasSelection()) {
- content->selectSkip(-1);
- }
- }
- return false;
- };
- return {
- .content = std::move(result),
- .overrideKey = overrideKey,
- .activate = activate,
- };
- }
- Controller::Controller(
- not_null<Main::Session*> session,
- Fn<void(not_null<PeerData*>)> choose)
- : ContactsBoxController(session)
- , _choose(std::move(choose))
- , _selfOption(MakeSelfOption(session, [=] { _choose(session->user()); })) {
- setStyleOverrides(&st::peerListSmallSkips);
- }
- void Controller::noSearchSubmit() {
- if (const auto onstack = _selfOption.activate) {
- onstack();
- }
- }
- bool Controller::overrideKeyboardNavigation(
- int direction,
- int fromIndex,
- int toIndex) {
- return _selfOption.overrideKey
- && _selfOption.overrideKey(direction, fromIndex, toIndex);
- }
- std::unique_ptr<PeerListRow> Controller::createRow(
- not_null<UserData*> user) {
- if (user->isSelf()
- || user->isBot()
- || user->isServiceUser()
- || user->isInaccessible()) {
- return nullptr;
- }
- return ContactsBoxController::createRow(user);
- }
- void Controller::prepareViewHook() {
- delegate()->peerListSetAboveWidget(std::move(_selfOption.content));
- }
- void Controller::rowClicked(not_null<PeerListRow*> row) {
- _choose(row->peer());
- }
- } // namespace
- void ChooseStarGiftRecipient(
- not_null<Window::SessionController*> window) {
- auto controller = std::make_unique<Controller>(
- &window->session(),
- [=](not_null<PeerData*> peer) {
- ShowStarGiftBox(window, peer);
- });
- const auto controllerRaw = controller.get();
- auto initBox = [=](not_null<PeerListBox*> box) {
- box->setTitle(tr::lng_gift_premium_or_stars());
- box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
- box->noSearchSubmits() | rpl::start_with_next([=] {
- controllerRaw->noSearchSubmit();
- }, box->lifetime());
- };
- window->show(
- Box<PeerListBox>(std::move(controller), std::move(initBox)),
- LayerOption::KeepOther);
- }
- void ShowStarGiftBox(
- not_null<Window::SessionController*> controller,
- not_null<PeerData*> peer) {
- struct Session {
- PeerData *peer = nullptr;
- bool premiumGiftsReady = false;
- bool starsGiftsReady = false;
- rpl::lifetime lifetime;
- };
- static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
- const auto session = &controller->session();
- auto i = Map.find(session);
- if (i == end(Map)) {
- i = Map.emplace(session).first;
- session->lifetime().add([=] { Map.remove(session); });
- } else if (i->second.peer == peer) {
- return;
- }
- i->second = Session{ .peer = peer };
- const auto weak = base::make_weak(controller);
- const auto show = [=] {
- Map[session] = Session();
- if (const auto strong = weak.get()) {
- strong->show(Box(GiftBox, strong, peer));
- }
- };
- base::timer_once(
- kGiftsPreloadTimeout
- ) | rpl::start_with_next(show, i->second.lifetime);
- const auto user = peer->asUser();
- if (user && !user->isSelf()) {
- GiftsPremium(
- session,
- peer
- ) | rpl::start_with_next([=](PremiumGiftsDescriptor &&gifts) {
- if (!gifts.list.empty()) {
- auto &entry = Map[session];
- entry.premiumGiftsReady = true;
- if (entry.starsGiftsReady) {
- show();
- }
- }
- }, i->second.lifetime);
- } else {
- i->second.premiumGiftsReady = true;
- }
- GiftsStars(
- session,
- peer
- ) | rpl::start_with_next([=](std::vector<GiftTypeStars> &&gifts) {
- if (!gifts.empty()) {
- auto &entry = Map[session];
- entry.starsGiftsReady = true;
- if (entry.premiumGiftsReady) {
- show();
- }
- }
- }, i->second.lifetime);
- }
- void AddUniqueGiftCover(
- not_null<VerticalLayout*> container,
- rpl::producer<Data::UniqueGift> data,
- rpl::producer<QString> subtitleOverride) {
- const auto cover = container->add(object_ptr<RpWidget>(container));
- const auto title = CreateChild<FlatLabel>(
- cover,
- rpl::duplicate(
- data
- ) | rpl::map([](const Data::UniqueGift &now) { return now.title; }),
- st::uniqueGiftTitle);
- title->setTextColorOverride(QColor(255, 255, 255));
- auto subtitleText = subtitleOverride
- ? std::move(subtitleOverride)
- : rpl::duplicate(data) | rpl::map([](const Data::UniqueGift &gift) {
- return tr::lng_gift_unique_number(
- tr::now,
- lt_index,
- QString::number(gift.number));
- });
- const auto subtitle = CreateChild<FlatLabel>(
- cover,
- std::move(subtitleText),
- st::uniqueGiftSubtitle);
- struct GiftView {
- QImage gradient;
- std::optional<Data::UniqueGift> gift;
- std::shared_ptr<Data::DocumentMedia> media;
- std::unique_ptr<Lottie::SinglePlayer> lottie;
- std::unique_ptr<Text::CustomEmoji> emoji;
- base::flat_map<float64, QImage> emojis;
- rpl::lifetime lifetime;
- };
- struct State {
- GiftView now;
- GiftView next;
- Animations::Simple crossfade;
- bool animating = false;
- };
- const auto state = cover->lifetime().make_state<State>();
- const auto lottieSize = st::creditsHistoryEntryStarGiftSize;
- const auto updateColors = [=](float64 progress) {
- subtitle->setTextColorOverride((progress == 0.)
- ? state->now.gift->backdrop.textColor
- : (progress == 1.)
- ? state->next.gift->backdrop.textColor
- : anim::color(
- state->now.gift->backdrop.textColor,
- state->next.gift->backdrop.textColor,
- progress));
- };
- std::move(
- data
- ) | rpl::start_with_next([=](const Data::UniqueGift &gift) {
- const auto setup = [&](GiftView &to) {
- to.gift = gift;
- const auto document = gift.model.document;
- to.media = document->createMediaView();
- to.media->automaticLoad({}, nullptr);
- rpl::single() | rpl::then(
- document->session().downloaderTaskFinished()
- ) | rpl::filter([&to] {
- return to.media->loaded();
- }) | rpl::start_with_next([=, &to] {
- const auto lottieSize = st::creditsHistoryEntryStarGiftSize;
- to.lottie = ChatHelpers::LottiePlayerFromDocument(
- to.media.get(),
- ChatHelpers::StickerLottieSize::MessageHistory,
- QSize(lottieSize, lottieSize),
- Lottie::Quality::High);
- to.lifetime.destroy();
- const auto lottie = to.lottie.get();
- lottie->updates() | rpl::start_with_next([=] {
- if (state->now.lottie.get() == lottie
- || state->crossfade.animating()) {
- cover->update();
- }
- }, to.lifetime);
- }, to.lifetime);
- to.emoji = document->owner().customEmojiManager().create(
- gift.pattern.document,
- [=] { cover->update(); },
- Data::CustomEmojiSizeTag::Large);
- [[maybe_unused]] const auto preload = to.emoji->ready();
- };
- if (!state->now.gift) {
- setup(state->now);
- cover->update();
- updateColors(0.);
- } else if (!state->next.gift) {
- setup(state->next);
- }
- }, cover->lifetime());
- cover->widthValue() | rpl::start_with_next([=](int width) {
- const auto skip = st::uniqueGiftBottom;
- if (width <= 3 * skip) {
- return;
- }
- const auto available = width - 2 * skip;
- title->resizeToWidth(available);
- title->moveToLeft(skip, st::uniqueGiftTitleTop);
- subtitle->resizeToWidth(available);
- subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop);
- cover->resize(width, subtitle->y() + subtitle->height() + skip);
- }, cover->lifetime());
- cover->paintRequest() | rpl::start_with_next([=] {
- auto p = QPainter(cover);
- auto progress = state->crossfade.value(state->animating ? 1. : 0.);
- if (state->animating) {
- updateColors(progress);
- }
- if (progress == 1.) {
- state->animating = false;
- state->now = base::take(state->next);
- progress = 0.;
- }
- const auto paint = [&](GiftView &gift, float64 shown) {
- Expects(gift.gift.has_value());
- const auto width = cover->width();
- const auto pointsHeight = st::uniqueGiftSubtitleTop;
- const auto ratio = style::DevicePixelRatio();
- if (gift.gradient.size() != cover->size() * ratio) {
- gift.gradient = CreateGradient(cover->size(), *gift.gift);
- }
- p.drawImage(0, 0, gift.gradient);
- PaintPoints(
- p,
- PatternPoints(),
- gift.emojis,
- gift.emoji.get(),
- *gift.gift,
- QRect(0, 0, width, pointsHeight),
- shown);
- const auto lottie = gift.lottie.get();
- const auto factor = style::DevicePixelRatio();
- const auto request = Lottie::FrameRequest{
- .box = Size(lottieSize) * factor,
- };
- const auto frame = (lottie && lottie->ready())
- ? lottie->frameInfo(request)
- : Lottie::Animation::FrameInfo();
- if (frame.image.isNull()) {
- return false;
- }
- const auto size = frame.image.size() / factor;
- const auto left = (width - size.width()) / 2;
- p.drawImage(
- QRect(QPoint(left, st::uniqueGiftModelTop), size),
- frame.image);
- const auto count = lottie->framesCount();
- const auto finished = lottie->frameIndex() == (count - 1);
- lottie->markFrameShown();
- return finished;
- };
- if (progress < 1.) {
- const auto finished = paint(state->now, 1. - progress);
- const auto next = finished ? state->next.lottie.get() : nullptr;
- if (next && next->ready()) {
- state->animating = true;
- state->crossfade.start([=] {
- cover->update();
- }, 0., 1., kCrossfadeDuration);
- }
- }
- if (progress > 0.) {
- p.setOpacity(progress);
- paint(state->next, progress);
- }
- }, cover->lifetime());
- }
- void AddWearGiftCover(
- not_null<VerticalLayout*> container,
- const Data::UniqueGift &data,
- not_null<PeerData*> peer) {
- const auto cover = container->add(object_ptr<RpWidget>(container));
- const auto title = CreateChild<FlatLabel>(
- cover,
- rpl::single(peer->name()),
- st::uniqueGiftTitle);
- title->setTextColorOverride(QColor(255, 255, 255));
- const auto subtitle = CreateChild<FlatLabel>(
- cover,
- (peer->isChannel()
- ? tr::lng_chat_status_subscribers(
- lt_count,
- rpl::single(peer->asChannel()->membersCount() * 1.))
- : tr::lng_status_online()),
- st::uniqueGiftSubtitle);
- subtitle->setTextColorOverride(data.backdrop.textColor);
- struct State {
- QImage gradient;
- Data::UniqueGift gift;
- Ui::PeerUserpicView view;
- std::unique_ptr<Text::CustomEmoji> emoji;
- base::flat_map<float64, QImage> emojis;
- rpl::lifetime lifetime;
- };
- const auto state = cover->lifetime().make_state<State>(State{
- .gift = data,
- });
- state->emoji = peer->owner().customEmojiManager().create(
- state->gift.pattern.document,
- [=] { cover->update(); },
- Data::CustomEmojiSizeTag::Large);
- cover->widthValue() | rpl::start_with_next([=](int width) {
- const auto skip = st::uniqueGiftBottom;
- if (width <= 3 * skip) {
- return;
- }
- const auto available = width - 2 * skip;
- title->resizeToWidth(available);
- title->moveToLeft(skip, st::uniqueGiftTitleTop);
- subtitle->resizeToWidth(available);
- subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop);
- cover->resize(width, subtitle->y() + subtitle->height() + skip);
- }, cover->lifetime());
- cover->paintRequest() | rpl::start_with_next([=] {
- auto p = Painter(cover);
- const auto width = cover->width();
- const auto pointsHeight = st::uniqueGiftSubtitleTop;
- const auto ratio = style::DevicePixelRatio();
- if (state->gradient.size() != cover->size() * ratio) {
- state->gradient = CreateGradient(cover->size(), state->gift);
- }
- p.drawImage(0, 0, state->gradient);
- PaintPoints(
- p,
- PatternPoints(),
- state->emojis,
- state->emoji.get(),
- state->gift,
- QRect(0, 0, width, pointsHeight),
- 1.);
- peer->paintUserpic(
- p,
- state->view,
- (width - st::uniqueGiftUserpicSize) / 2,
- st::uniqueGiftUserpicTop,
- st::uniqueGiftUserpicSize);
- }, cover->lifetime());
- }
- void ShowUniqueGiftWearBox(
- std::shared_ptr<ChatHelpers::Show> show,
- not_null<PeerData*> peer,
- const Data::UniqueGift &gift,
- Settings::GiftWearBoxStyleOverride st) {
- show->show(Box([=](not_null<Ui::GenericBox*> box) {
- box->setNoContentMargin(true);
- box->setWidth((st::boxWidth + st::boxWideWidth) / 2); // =)
- box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
- const auto channel = peer->isChannel();
- const auto content = box->verticalLayout();
- AddWearGiftCover(content, gift, peer);
- AddSkip(content, st::defaultVerticalListSkip * 2);
- const auto infoRow = [&](
- rpl::producer<QString> title,
- rpl::producer<QString> text,
- not_null<const style::icon*> icon) {
- auto raw = content->add(
- object_ptr<Ui::VerticalLayout>(content));
- raw->add(
- object_ptr<Ui::FlatLabel>(
- raw,
- std::move(title) | Ui::Text::ToBold(),
- st.infoTitle ? *st.infoTitle : st::defaultFlatLabel),
- st::settingsPremiumRowTitlePadding);
- raw->add(
- object_ptr<Ui::FlatLabel>(
- raw,
- std::move(text),
- st.infoAbout ? *st.infoAbout : st::upgradeGiftSubtext),
- st::settingsPremiumRowAboutPadding);
- object_ptr<Info::Profile::FloatingIcon>(
- raw,
- *icon,
- st::starrefInfoIconPosition);
- };
- content->add(
- object_ptr<Ui::FlatLabel>(
- content,
- tr::lng_gift_wear_title(
- lt_name,
- rpl::single(UniqueGiftName(gift))),
- st.title ? *st.title : st::uniqueGiftTitle),
- st::settingsPremiumRowTitlePadding);
- content->add(
- object_ptr<Ui::FlatLabel>(
- content,
- tr::lng_gift_wear_about(),
- st.subtitle ? *st.subtitle : st::uniqueGiftSubtitle),
- st::settingsPremiumRowAboutPadding);
- infoRow(
- tr::lng_gift_wear_badge_title(),
- (channel
- ? tr::lng_gift_wear_badge_about_channel()
- : tr::lng_gift_wear_badge_about()),
- st.radiantIcon ? st.radiantIcon : &st::menuIconUnique);
- //infoRow(
- // tr::lng_gift_wear_design_title(),
- // tr::lng_gift_wear_design_about(),
- // &st::menuIconUniqueProfile);
- infoRow(
- tr::lng_gift_wear_proof_title(),
- (channel
- ? tr::lng_gift_wear_proof_about_channel()
- : tr::lng_gift_wear_proof_about()),
- st.proofIcon ? st.proofIcon : &st::menuIconFactcheck);
- const auto session = &show->session();
- const auto checking = std::make_shared<bool>();
- const auto button = box->addButton(rpl::single(QString()), [=] {
- const auto emojiStatuses = &session->data().emojiStatuses();
- const auto id = emojiStatuses->fromUniqueGift(gift);
- if (!peer->isSelf()) {
- if (*checking) {
- return;
- }
- *checking = true;
- const auto weak = Ui::MakeWeak(box);
- CheckBoostLevel(show, peer, [=](int level) {
- const auto limits = Data::LevelLimits(&peer->session());
- const auto wanted = limits.channelEmojiStatusLevelMin();
- if (level >= wanted) {
- if (const auto strong = weak.data()) {
- strong->closeBox();
- }
- emojiStatuses->set(peer, id);
- return std::optional<Ui::AskBoostReason>();
- }
- const auto reason = [&]() -> Ui::AskBoostReason {
- return { Ui::AskBoostWearCollectible{
- wanted
- } };
- }();
- return std::make_optional(reason);
- }, [=] { *checking = false; });
- } else if (session->premium()) {
- box->closeBox();
- emojiStatuses->set(peer, id);
- } else {
- const auto link = Ui::Text::Bold(
- tr::lng_send_as_premium_required_link(tr::now));
- Settings::ShowPremiumPromoToast(
- show,
- tr::lng_gift_wear_subscribe(
- tr::now,
- lt_link,
- Ui::Text::Link(link),
- Ui::Text::WithEntities),
- u"wear_collectibles"_q);
- }
- });
- const auto lock = Ui::Text::SingleCustomEmoji(
- session->data().customEmojiManager().registerInternalEmoji(
- st::historySendDisabledIcon,
- st::giftBoxLockMargins,
- true));
- auto label = rpl::combine(
- tr::lng_gift_wear_start(),
- Data::AmPremiumValue(&show->session())
- ) | rpl::map([=](const QString &text, bool premium) {
- auto result = TextWithEntities();
- if (!premium && peer->isSelf()) {
- result.append(lock);
- }
- result.append(text);
- return result;
- });
- SetButtonMarkedLabel(
- button,
- std::move(label),
- session,
- st::creditsBoxButtonLabel,
- &st::giftBox.button.textFg);
- rpl::combine(
- box->widthValue(),
- button->widthValue()
- ) | rpl::start_with_next([=](int outer, int inner) {
- const auto padding = st::giftBox.buttonPadding;
- const auto wanted = outer - padding.left() - padding.right();
- if (inner != wanted) {
- button->resizeToWidth(wanted);
- button->moveToLeft(padding.left(), padding.top());
- }
- }, box->lifetime());
- AddUniqueCloseButton(box, {});
- }));
- }
- struct UpgradeArgs : StarGiftUpgradeArgs {
- std::vector<Data::UniqueGiftModel> models;
- std::vector<Data::UniqueGiftPattern> patterns;
- std::vector<Data::UniqueGiftBackdrop> backdrops;
- };
- [[nodiscard]] rpl::producer<Data::UniqueGift> MakeUpgradeGiftStream(
- const UpgradeArgs &args) {
- if (args.models.empty()
- || args.patterns.empty()
- || args.backdrops.empty()) {
- return rpl::never<Data::UniqueGift>();
- }
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- struct State {
- UpgradeArgs data;
- std::vector<int> modelIndices;
- std::vector<int> patternIndices;
- std::vector<int> backdropIndices;
- };
- const auto state = lifetime.make_state<State>(State{
- .data = args,
- });
- const auto put = [=] {
- const auto index = [](std::vector<int> &indices, const auto &v) {
- const auto fill = [&] {
- if (!indices.empty()) {
- return;
- }
- indices = ranges::views::ints(0) | ranges::views::take(
- v.size()
- ) | ranges::to_vector;
- ranges::shuffle(indices);
- };
- fill();
- const auto result = indices.back();
- indices.pop_back();
- fill();
- if (indices.back() == result) {
- std::swap(indices.front(), indices.back());
- }
- return result;
- };
- auto &models = state->data.models;
- auto &patterns = state->data.patterns;
- auto &backdrops = state->data.backdrops;
- consumer.put_next(Data::UniqueGift{
- .title = (state->data.savedId
- ? tr::lng_gift_upgrade_title(tr::now)
- : tr::lng_gift_upgrade_preview_title(tr::now)),
- .model = models[index(state->modelIndices, models)],
- .pattern = patterns[index(state->patternIndices, patterns)],
- .backdrop = backdrops[index(state->backdropIndices, backdrops)],
- });
- };
- put();
- base::timer_each(
- kSwitchUpgradeCoverInterval / 3
- ) | rpl::start_with_next(put, lifetime);
- return lifetime;
- };
- }
- void AddUpgradeGiftCover(
- not_null<VerticalLayout*> container,
- const UpgradeArgs &args) {
- AddUniqueGiftCover(
- container,
- MakeUpgradeGiftStream(args),
- (args.savedId
- ? tr::lng_gift_upgrade_about()
- : (args.peer->isBroadcast()
- ? tr::lng_gift_upgrade_preview_about_channel
- : tr::lng_gift_upgrade_preview_about)(
- lt_name,
- rpl::single(args.peer->shortName()))));
- }
- void UpgradeBox(
- not_null<GenericBox*> box,
- not_null<Window::SessionController*> controller,
- UpgradeArgs &&args) {
- box->setNoContentMargin(true);
- const auto container = box->verticalLayout();
- AddUpgradeGiftCover(container, args);
- AddSkip(container, st::defaultVerticalListSkip * 2);
- const auto infoRow = [&](
- rpl::producer<QString> title,
- rpl::producer<QString> text,
- not_null<const style::icon*> icon) {
- auto raw = container->add(
- object_ptr<Ui::VerticalLayout>(container));
- raw->add(
- object_ptr<Ui::FlatLabel>(
- raw,
- std::move(title) | Ui::Text::ToBold(),
- st::defaultFlatLabel),
- st::settingsPremiumRowTitlePadding);
- raw->add(
- object_ptr<Ui::FlatLabel>(
- raw,
- std::move(text),
- st::upgradeGiftSubtext),
- st::settingsPremiumRowAboutPadding);
- object_ptr<Info::Profile::FloatingIcon>(
- raw,
- *icon,
- st::starrefInfoIconPosition);
- };
- infoRow(
- tr::lng_gift_upgrade_unique_title(),
- tr::lng_gift_upgrade_unique_about(),
- &st::menuIconUnique);
- infoRow(
- tr::lng_gift_upgrade_transferable_title(),
- tr::lng_gift_upgrade_transferable_about(),
- &st::menuIconReplace);
- infoRow(
- tr::lng_gift_upgrade_tradable_title(),
- tr::lng_gift_upgrade_tradable_about(),
- &st::menuIconTradable);
- struct State {
- bool sent = false;
- bool preserveDetails = false;
- };
- const auto state = std::make_shared<State>();
- const auto preview = !args.savedId;
- if (!preview) {
- const auto skip = st::defaultVerticalListSkip;
- container->add(
- object_ptr<PlainShadow>(container),
- st::boxRowPadding + QMargins(0, skip, 0, skip));
- const auto checkbox = container->add(
- object_ptr<CenterWrap<Checkbox>>(
- container,
- object_ptr<Checkbox>(
- container,
- (args.canAddComment
- ? tr::lng_gift_upgrade_add_comment(tr::now)
- : args.canAddSender
- ? tr::lng_gift_upgrade_add_sender(tr::now)
- : args.canAddMyComment
- ? tr::lng_gift_upgrade_add_my_comment(tr::now)
- : tr::lng_gift_upgrade_add_my(tr::now)),
- args.addDetailsDefault)),
- st::defaultCheckbox.margin)->entity();
- checkbox->checkedChanges() | rpl::start_with_next([=](bool checked) {
- state->preserveDetails = checked;
- }, checkbox->lifetime());
- }
- box->setStyle(preview ? st::giftBox : st::upgradeGiftBox);
- const auto cost = args.cost;
- const auto session = &controller->session();
- auto buttonText = preview ? tr::lng_box_ok() : rpl::single(QString());
- const auto button = box->addButton(std::move(buttonText), [=] {
- if (preview) {
- box->closeBox();
- return;
- } else if (state->sent) {
- return;
- }
- state->sent = true;
- const auto keepDetails = state->preserveDetails;
- const auto weak = Ui::MakeWeak(box);
- const auto done = [=](Payments::CheckoutResult result) {
- if (result != Payments::CheckoutResult::Paid) {
- state->sent = false;
- } else {
- controller->showPeerHistory(args.peer);
- if (const auto strong = weak.data()) {
- strong->closeBox();
- }
- }
- };
- UpgradeGift(controller, args.savedId, keepDetails, cost, done);
- });
- if (!preview) {
- auto star = session->data().customEmojiManager().creditsEmoji();
- SetButtonMarkedLabel(
- button,
- (cost
- ? tr::lng_gift_upgrade_button(
- lt_price,
- rpl::single(star.append(
- ' ' + Lang::FormatStarsAmountDecimal(
- StarsAmount{ cost }))),
- Ui::Text::WithEntities)
- : tr::lng_gift_upgrade_confirm(Ui::Text::WithEntities)),
- &controller->session(),
- st::creditsBoxButtonLabel,
- &st::giftBox.button.textFg);
- }
- rpl::combine(
- box->widthValue(),
- button->widthValue()
- ) | rpl::start_with_next([=](int outer, int inner) {
- const auto padding = st::giftBox.buttonPadding;
- const auto wanted = outer - padding.left() - padding.right();
- if (inner != wanted) {
- button->resizeToWidth(wanted);
- button->moveToLeft(padding.left(), padding.top());
- }
- }, box->lifetime());
- AddUniqueCloseButton(box, {});
- }
- const std::vector<PatternPoint> &PatternPoints() {
- static const auto kSmall = 0.7;
- static const auto kFaded = 0.2;
- static const auto kLarge = 0.85;
- static const auto kOpaque = 0.3;
- static const auto result = std::vector<PatternPoint>{
- { { 0.5, 0.066 }, kSmall, kFaded },
- { { 0.177, 0.168 }, kSmall, kFaded },
- { { 0.822, 0.168 }, kSmall, kFaded },
- { { 0.37, 0.168 }, kLarge, kOpaque },
- { { 0.63, 0.168 }, kLarge, kOpaque },
- { { 0.277, 0.308 }, kSmall, kOpaque },
- { { 0.723, 0.308 }, kSmall, kOpaque },
- { { 0.13, 0.42 }, kSmall, kFaded },
- { { 0.87, 0.42 }, kSmall, kFaded },
- { { 0.27, 0.533 }, kLarge, kOpaque },
- { { 0.73, 0.533 }, kLarge, kOpaque },
- { { 0.2, 0.73 }, kSmall, kFaded },
- { { 0.8, 0.73 }, kSmall, kFaded },
- { { 0.302, 0.825 }, kLarge, kOpaque },
- { { 0.698, 0.825 }, kLarge, kOpaque },
- { { 0.5, 0.876 }, kLarge, kFaded },
- { { 0.144, 0.936 }, kSmall, kFaded },
- { { 0.856, 0.936 }, kSmall, kFaded },
- };
- return result;
- }
- const std::vector<PatternPoint> &PatternPointsSmall() {
- static const auto kSmall = 0.45;
- static const auto kFaded = 0.2;
- static const auto kLarge = 0.55;
- static const auto kOpaque = 0.3;
- static const auto result = std::vector<PatternPoint>{
- { { 0.5, 0.066 }, kSmall, kFaded },
- { { 0.177, 0.168 }, kSmall, kFaded },
- { { 0.822, 0.168 }, kSmall, kFaded },
- { { 0.37, 0.168 }, kLarge, kOpaque },
- { { 0.63, 0.168 }, kLarge, kOpaque },
- { { 0.277, 0.308 }, kSmall, kOpaque },
- { { 0.723, 0.308 }, kSmall, kOpaque },
- { { 0.13, 0.42 }, kSmall, kFaded },
- { { 0.87, 0.42 }, kSmall, kFaded },
- { { 0.27, 0.533 }, kLarge, kOpaque },
- { { 0.73, 0.533 }, kLarge, kOpaque },
- { { 0.2, 0.73 }, kSmall, kFaded },
- { { 0.8, 0.73 }, kSmall, kFaded },
- { { 0.302, 0.825 }, kLarge, kOpaque },
- { { 0.698, 0.825 }, kLarge, kOpaque },
- { { 0.5, 0.876 }, kLarge, kFaded },
- { { 0.144, 0.936 }, kSmall, kFaded },
- { { 0.856, 0.936 }, kSmall, kFaded },
- };
- return result;
- }
- void PaintPoints(
- QPainter &p,
- const std::vector<PatternPoint> &points,
- base::flat_map<float64, QImage> &cache,
- not_null<Text::CustomEmoji*> emoji,
- const Data::UniqueGift &gift,
- const QRect &rect,
- float64 shown) {
- const auto origin = rect.topLeft();
- const auto width = rect.width();
- const auto height = rect.height();
- const auto ratio = style::DevicePixelRatio();
- const auto paintPoint = [&](const PatternPoint &point) {
- const auto key = (1. + point.opacity) * 10. + point.scale;
- auto &image = cache[key];
- PrepareImage(image, emoji, point, gift);
- if (!image.isNull()) {
- const auto position = origin + QPoint(
- int(point.position.x() * width),
- int(point.position.y() * height));
- if (shown < 1.) {
- p.save();
- p.translate(position);
- p.scale(shown, shown);
- p.translate(-position);
- }
- const auto size = image.size() / ratio;
- p.drawImage(
- position - QPoint(size.width() / 2, size.height() / 2),
- image);
- if (shown < 1.) {
- p.restore();
- }
- }
- };
- for (const auto &point : points) {
- paintPoint(point);
- }
- }
- void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
- const auto weak = base::make_weak(args.controller);
- const auto session = &args.peer->session();
- session->api().request(MTPpayments_GetStarGiftUpgradePreview(
- MTP_long(args.stargiftId)
- )).done([=](const MTPpayments_StarGiftUpgradePreview &result) {
- const auto strong = weak.get();
- if (!strong) {
- if (const auto onstack = args.ready) {
- onstack(false);
- }
- return;
- }
- const auto &data = result.data();
- auto upgrade = UpgradeArgs{ args };
- for (const auto &attribute : data.vsample_attributes().v) {
- attribute.match([&](const MTPDstarGiftAttributeModel &data) {
- upgrade.models.push_back(Api::FromTL(session, data));
- }, [&](const MTPDstarGiftAttributePattern &data) {
- upgrade.patterns.push_back(Api::FromTL(session, data));
- }, [&](const MTPDstarGiftAttributeBackdrop &data) {
- upgrade.backdrops.push_back(Api::FromTL(data));
- }, [](const auto &) {});
- }
- strong->show(Box(UpgradeBox, strong, std::move(upgrade)));
- if (const auto onstack = args.ready) {
- onstack(true);
- }
- }).fail([=](const MTP::Error &error) {
- if (const auto strong = weak.get()) {
- strong->showToast(error.type());
- }
- if (const auto onstack = args.ready) {
- onstack(false);
- }
- }).send();
- }
- void AddUniqueCloseButton(
- not_null<GenericBox*> box,
- Settings::CreditsEntryBoxStyleOverrides st,
- Fn<void(not_null<Ui::PopupMenu*>)> fillMenu) {
- const auto close = Ui::CreateChild<IconButton>(
- box,
- st::uniqueCloseButton);
- const auto menu = fillMenu
- ? Ui::CreateChild<IconButton>(box, st::uniqueMenuButton)
- : nullptr;
- close->show();
- close->raise();
- if (menu) {
- menu->show();
- menu->raise();
- }
- box->widthValue() | rpl::start_with_next([=](int width) {
- close->moveToRight(0, 0, width);
- close->raise();
- if (menu) {
- menu->moveToRight(close->width(), 0, width);
- menu->raise();
- }
- }, close->lifetime());
- close->setClickedCallback([=] {
- box->closeBox();
- });
- if (menu) {
- const auto state = menu->lifetime().make_state<
- base::unique_qptr<Ui::PopupMenu>
- >();
- menu->setClickedCallback([=] {
- if (*state) {
- *state = nullptr;
- return;
- }
- *state = base::make_unique_q<Ui::PopupMenu>(
- menu,
- st.menu ? *st.menu : st::popupMenuWithIcons);
- fillMenu(state->get());
- if (!(*state)->empty()) {
- (*state)->popup(QCursor::pos());
- }
- });
- }
- }
- void RequestStarsFormAndSubmit(
- not_null<Window::SessionController*> window,
- MTPInputInvoice invoice,
- Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done) {
- const auto weak = base::make_weak(window);
- window->session().api().request(MTPpayments_GetPaymentForm(
- MTP_flags(0),
- invoice,
- MTPDataJSON() // theme_params
- )).done([=](const MTPpayments_PaymentForm &result) {
- result.match([&](const MTPDpayments_paymentFormStarGift &data) {
- const auto formId = data.vform_id().v;
- const auto prices = data.vinvoice().data().vprices().v;
- const auto strong = weak.get();
- if (!strong) {
- done(Payments::CheckoutResult::Failed, nullptr);
- return;
- }
- const auto ready = [=](Settings::SmallBalanceResult result) {
- SendStarsFormRequest(strong, result, formId, invoice, done);
- };
- Settings::MaybeRequestBalanceIncrease(
- strong->uiShow(),
- prices.front().data().vamount().v,
- Settings::SmallBalanceDeepLink{},
- ready);
- }, [&](const auto &) {
- done(Payments::CheckoutResult::Failed, nullptr);
- });
- }).fail([=](const MTP::Error &error) {
- const auto type = error.type();
- if (type == u"STARGIFT_EXPORT_IN_PROGRESS"_q) {
- done(Payments::CheckoutResult::Cancelled, nullptr);
- } else {
- if (const auto strong = weak.get()) {
- strong->showToast(type);
- }
- done(Payments::CheckoutResult::Failed, nullptr);
- }
- }).send();
- }
- void ShowGiftTransferredToast(
- base::weak_ptr<Window::SessionController> weak,
- not_null<PeerData*> to,
- const Data::UniqueGift &gift) {
- if (const auto strong = weak.get()) {
- strong->showToast({
- .title = tr::lng_gift_transferred_title(tr::now),
- .text = tr::lng_gift_transferred_about(
- tr::now,
- lt_name,
- Text::Bold(Data::UniqueGiftName(gift)),
- lt_recipient,
- Text::Bold(to->shortName()),
- Ui::Text::WithEntities),
- .duration = kUpgradeDoneToastDuration,
- });
- }
- }
- } // namespace Ui
|