From 7d2878d81cc0eadd83c004f969e2583635aa0b2d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 Mar 2025 14:13:39 +0400 Subject: [PATCH] Support gifting premium for stars. --- .../icons/payments/premium_emoji.png | Bin 0 -> 370 bytes .../icons/payments/premium_emoji@2x.png | Bin 0 -> 712 bytes .../icons/payments/premium_emoji@3x.png | Bin 0 -> 926 bytes Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/api/api_premium.cpp | 3 + Telegram/SourceFiles/boxes/boxes.style | 5 - .../SourceFiles/boxes/send_credits_box.cpp | 4 +- Telegram/SourceFiles/boxes/send_credits_box.h | 2 +- Telegram/SourceFiles/boxes/star_gift_box.cpp | 110 +++++++++++++++--- .../chat_helpers/message_field.cpp | 3 +- Telegram/SourceFiles/core/shortcuts.cpp | 8 ++ .../data/stickers/data_custom_emoji.cpp | 9 ++ .../data/stickers/data_custom_emoji.h | 1 + .../peer_gifts/info_peer_gifts_common.cpp | 52 +++++++-- .../info/peer_gifts/info_peer_gifts_common.h | 3 + .../payments/payments_checkout_process.cpp | 29 ++--- .../payments/payments_checkout_process.h | 3 +- .../SourceFiles/payments/payments_form.cpp | 35 ++++-- Telegram/SourceFiles/payments/payments_form.h | 2 + .../payments/payments_non_panel_process.cpp | 5 +- .../SourceFiles/settings/settings_credits.cpp | 106 ++++++++++------- .../SourceFiles/settings/settings_credits.h | 23 +++- .../settings/settings_credits_graphics.cpp | 4 +- .../SourceFiles/ui/controls/send_button.cpp | 3 +- Telegram/SourceFiles/ui/effects/credits.style | 17 ++- Telegram/lib_ui | 2 +- 26 files changed, 321 insertions(+), 112 deletions(-) create mode 100644 Telegram/Resources/icons/payments/premium_emoji.png create mode 100644 Telegram/Resources/icons/payments/premium_emoji@2x.png create mode 100644 Telegram/Resources/icons/payments/premium_emoji@3x.png diff --git a/Telegram/Resources/icons/payments/premium_emoji.png b/Telegram/Resources/icons/payments/premium_emoji.png new file mode 100644 index 0000000000000000000000000000000000000000..e8b8fcf292b6f466acfdcc9d938efbbb44ff8551 GIT binary patch literal 370 zcmV-&0ge8NP))s#DvaIX6RaL$AMhLA|tBEh#ZnqfY&t)KlBl9;pUDvPIE5k6KjYLs=9%^*9ZBNnw z07X$I0-B~hOi>iB>w2EoG|gw=Dc|>#Byk*PoU>Rgj>luuG^f*Pa`G(w1N8;rTRq58 Q#Q*>R07*qoM6N<$f-be3$^ZZW literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/payments/premium_emoji@2x.png b/Telegram/Resources/icons/payments/premium_emoji@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8824f11b97c6f1051e2e8f9d9e97022a6658631b GIT binary patch literal 712 zcmV;(0yq7MP)CmOpFiP#DHfOah{{S_i3CB@Tj?B06>IP#m=AQk(># zNL#SD2~t--fKqVr&n!q6`xQh)aVW*5D%v3g3=vz3MzJlPJ9sO_s7=}|m-`HX9NzPL zp7Wl=NdWkdWV6{ioldLOGMmlf(@_+Ce0+ouLI_KxlK2K^XJ_z>oKB~hbR5T@o}RuM zR4NrQ4X&=PAV)5jOB}jXDy1ljYf!7z#4(6OB3$}h9*^g@bcI5}Fw8oG`}=zw$2SvD z?huAyMx)Vgw;vxLCzDBw#jfN5|(9Wv)NJ&0sv5})t8r-R;!gD z2!bH=dOeD+F#c8$LYk&Yk|as8-EODTX^Nr%0H9nhuWQ0@^ziTi0FVFx4h{}}koQ}u zR4OpvgSWRg(GK;eK0ZD!E-sqQ=HGXp&o>+n#cIxUIz2r-UG}tEt&8hyrC642G#d4K zeKUPL9=qM{1s3`0f#Y~E81(!7YPEVgop!t3ZnxX%bmq@qucy&y;_-MW6k6Ar(P-rL zdVg}=?(Xg;5($=NH)MZ)em*=r6pYsCbT>CQ~_20)TvZzGMQ{f&y~yN zRzk3K^plg5d_KRkv%@ouTrL-oeSd$SXYhrp)v5r4+uK_~bO<3$(|qX&g1o-I3N?Tb z%49MwO~M^A7!2Fn+XB}6DS{w)8La#584L!=WHOu0P9~F;1tyb;R}k(ezTfZ1VzI5Q zt$D%T-d-k?d4GRjDhr3hybJ*pP%zGz%VS4NYv}~ uXf*om>2^3AcXxOFe!o~OuH58*Uw;6mxjm>12Dz;O00008UrDyfJmQehMY&P;mWVMXR_&TVaceEkoy z!`}AWp7Y@V`0f8|dV2cq?vAEuhr^+^1%RQUA^6ej>uYOktC|`d$7z~|8SU=wstFn! z8-tu|Hk&#ctyX(;bHfGg?d_=pnwXg2YS=nEJC)Yxbh`Wd`|m*e`}<0RW@cu-)3Dj? zcI7mBz5eOxi63-ybfg?;Zf=fW!`9Q&qlAVah(sbG1ay3Sj3CI5gZzHK5DnYN$jDDQ z5Ck!qOfHv8xCTP_^71l1K3-Z{TEL08CngBOVzK=Bn#E!%E-n^*J&8nurfHg{Baukv zMkEqRCX+b^kR-XhynKFs&M-_t_dt5Lx3|Gyu)V$gQ)4g~9v&VPc4U=GrK+l`001Ze zps%m5q@+ajbjT8hVO?Ea?;wU@)YY)XVzKuvolbXlcBbx>1p)ySML%@}L4-mfwKQvM zYdDT)-!Tlsd_JG@nw6CmjYjkBL=FxP3S1dvX=!Qc%S`30uC9)vD4wIdy}d=F(Vd;0 z#l^*`sj2?{eyi0=5X8yJNkJN)&&OTtaxZ=wjmB=bI~)#@BvBNN$K#immlqco?B~_h zRn~)PwOZY7cXxMpo@>p>&XpN1H>+9=<&Xn8jmY>qx+#Co53Z*~@old9B zl$Ms3U@$0x0wEk79hH;P*4DPQwIz-MAquOt8-k4LZ9OTDa*Dk>`YcgO=W8jS-319EAA zbWle}hkO_Sq=V|~>*d1$ARSa)T`eC50A)a_R4Nn-Q4}Q$h%XGo(&;qMGn`B&{eFL0 zSsA;3Q&ZFS_IBPUU#r!M1l82k@K6>O7R+Wdca+U$J2*Jt0g)sr5>#7T%RL;!F!S^C zCXFH@skofNSL^j^j?JGaL>-KR>Uot`_v-;kSRHzp#(07FGK~82|tP07*qoM6N<$f@~_S AxBvhE literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 294adcadb..250ba9584 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3329,6 +3329,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}"; "lng_gift_premium_features" = "See Features >"; "lng_gift_premium_label" = "Premium"; +"lng_gift_premium_by_stars" = "or {amount}"; "lng_gift_stars_subtitle" = "Gift Stars"; "lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}"; "lng_gift_stars_link" = "What are Stars >"; @@ -3340,6 +3341,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_send_title" = "Send a Gift"; "lng_gift_send_message" = "Enter Message"; "lng_gift_send_anonymous" = "Hide My Name"; +"lng_gift_send_pay_with_stars" = "Pay with {amount}"; +"lng_gift_send_stars_balance" = "Your balance is {amount}. {link}"; +"lng_gift_send_stars_balance_link" = "Get More Stars >"; "lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile."; "lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message."; "lng_gift_send_anonymous_about_paid" = "You can hide your name from visitors to {user}'s profile. {recipient} will still see your name."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index fe3004cda..daa9d83cb 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -479,6 +479,9 @@ rpl::producer PremiumGiftCodeOptions::request() { for (const auto &tlOption : result.v) { const auto &data = tlOption.data(); tlMapOptions[data.vusers().v].push_back(tlOption); + if (qs(data.vcurrency()) == Ui::kCreditsCurrency) { + continue; + } const auto token = Token{ data.vusers().v, data.vmonths().v }; _stores[token] = Store{ diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index fc3421394..52fb1b43b 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -30,11 +30,6 @@ ShortInfoBox { labeledOneLine: FlatLabel; } -boxStarIconEmoji: IconEmoji { - icon: icon{{ "payments/small_star", windowFg }}; - padding: margins(0px, -2px, 0px, 0px); -} - countryRowHeight: 36px; countryRowNameFont: semiboldFont; countryRowNameFg: boxTextFg; diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index d1e74eeed..8091e7ea4 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -512,7 +512,7 @@ TextWithEntities CreditsEmoji(not_null session) { TextWithEntities CreditsEmojiSmall(not_null session) { return Ui::Text::IconEmoji( - &st::boxStarIconEmoji, + &st::starIconEmoji, QString(QChar(0x2B50))); } @@ -570,7 +570,7 @@ not_null SetButtonMarkedLabel( }, st, textFg); } -void SendStarGift( +void SendStarsForm( not_null session, std::shared_ptr data, Fn)> done) { diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index cc84b379e..e51dc39b3 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -52,7 +52,7 @@ not_null SetButtonMarkedLabel( const style::FlatLabel &st, const style::color *textFg = nullptr); -void SendStarGift( +void SendStarsForm( not_null session, std::shared_ptr data, Fn)> done); diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index fcd18526b..89c53f604 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -128,6 +128,7 @@ struct GiftDetails { uint64 randomId = 0; bool anonymous = false; bool upgraded = false; + bool byStars = false; }; class PreviewDelegate final : public DefaultElementDelegate { @@ -497,7 +498,14 @@ void PreviewWrap::prepare(rpl::producer details) { std::move(details) | rpl::start_with_next([=](GiftDetails details) { const auto &descriptor = details.descriptor; const auto cost = v::match(descriptor, [&](GiftTypePremium data) { - return FillAmountAndCurrency(data.cost, data.currency, true); + 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); @@ -1118,16 +1126,35 @@ void SendGift( std::shared_ptr api, const GiftDetails &details, Fn done) { + const auto processNonPanelPaymentFormFactory + = Payments::ProcessNonPanelPaymentFormFactory(window, done); v::match(details.descriptor, [&](const GiftTypePremium &gift) { - auto invoice = api->invoice(1, gift.months); - invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ - .users = { peer->asUser() }, - .message = details.text, - }; - Payments::CheckoutProcess::Start(std::move(invoice), done); + 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) { - const auto processNonPanelPaymentFormFactory - = Payments::ProcessNonPanelPaymentFormFactory(window, done); Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{ .giftId = gift.info.id, .randomId = details.randomId, @@ -1459,9 +1486,14 @@ void SendGiftBox( auto cost = state->details.value( ) | rpl::map([session](const GiftDetails &details) { return v::match(details.descriptor, [&](const GiftTypePremium &data) { - if (data.currency == kCreditsCurrency) { + 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(data.cost))); + Lang::FormatCountDecimal(std::abs(stars))); } return TextWithEntities{ FillAmountAndCurrency(data.cost, data.currency), @@ -1580,10 +1612,56 @@ void SendGiftBox( }, container->lifetime()); AddSkip(container); } - v::match(descriptor, [&](const GiftTypePremium &) { + 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( + 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 loading; + }; + const auto state = balance->lifetime().make_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() @@ -1618,9 +1696,13 @@ void SendGiftBox( const auto weak = MakeWeak(box); const auto done = [=](Payments::CheckoutResult result) { if (result == Payments::CheckoutResult::Paid) { + if (details.byStars + || v::is(details.descriptor)) { + window->session().credits().load(true); + } const auto copy = state->media; window->showPeerHistory(peer); - ShowSentToast(window, descriptor, details); + ShowSentToast(window, details.descriptor, details); } if (const auto strong = weak.data()) { box->closeBox(); @@ -1853,6 +1935,8 @@ void GiftBox( box->setCustomCornersFilling(RectPart::FullTop); box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); }); + window->session().credits().load(); + FillBg(box); const auto &stUser = st::premiumGiftsUserpicButton; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 617051762..927f1359c 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" +#include "styles/style_credits.h" #include "styles/style_settings.h" #include "base/qt/qt_common_adapters.h" @@ -1282,7 +1283,7 @@ void SelectTextInFieldWithMargins( } TextWithEntities PaidSendButtonText(tr::now_t, int stars) { - return Ui::Text::IconEmoji(&st::boxStarIconEmoji).append( + return Ui::Text::IconEmoji(&st::starIconEmoji).append( Lang::FormatCountToShort(stars).string); } diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index 371ef6a6d..eaad16558 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -76,6 +76,14 @@ const auto CommandByName = base::flat_map{ { u"first_chat"_q , Command::ChatFirst }, { u"last_chat"_q , Command::ChatLast }, { u"self_chat"_q , Command::ChatSelf }, + { u"pinned_chat1"_q , Command::ChatPinned1 }, + { u"pinned_chat2"_q , Command::ChatPinned2 }, + { u"pinned_chat3"_q , Command::ChatPinned3 }, + { u"pinned_chat4"_q , Command::ChatPinned4 }, + { u"pinned_chat5"_q , Command::ChatPinned5 }, + { u"pinned_chat6"_q , Command::ChatPinned6 }, + { u"pinned_chat7"_q , Command::ChatPinned7 }, + { u"pinned_chat8"_q , Command::ChatPinned8 }, { u"previous_folder"_q , Command::FolderPrevious }, { u"next_folder"_q , Command::FolderNext }, diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 695cb06ee..f541fda01 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" +#include "styles/style_credits.h" // giftBoxByStarsStyle namespace Data { namespace { @@ -1027,6 +1028,14 @@ TextWithEntities CustomEmojiManager::creditsEmoji(QMargins padding) { false)); } +TextWithEntities CustomEmojiManager::ministarEmoji(QMargins padding) { + return Ui::Text::SingleCustomEmoji( + registerInternalEmoji( + Ui::GenerateStars(st::giftBoxByStarsStyle.font->height, 1), + padding, + false)); +} + QString CustomEmojiManager::registerInternalEmoji( QImage emoji, QMargins padding, diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 60fd75f06..b01f7561e 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -100,6 +100,7 @@ public: [[nodiscard]] uint64 coloredSetId() const; [[nodiscard]] TextWithEntities creditsEmoji(QMargins padding = {}); + [[nodiscard]] TextWithEntities ministarEmoji(QMargins padding = {}); private: static constexpr auto kSizeCount = int(SizeTag::kCount); diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index 798d2180c..98b521566 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -85,13 +85,11 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { unsubscribe(); v::match(descriptor, [&](const GiftTypePremium &data) { const auto months = data.months; - const auto years = (months % 12) ? 0 : months / 12; _text = Ui::Text::String(st::giftBoxGiftHeight / 4); _text.setMarkedText( st::defaultTextStyle, - Ui::Text::Bold(years - ? tr::lng_years(tr::now, lt_count, years) - : tr::lng_months(tr::now, lt_count, months) + Ui::Text::Bold( + tr::lng_months(tr::now, lt_count, months) ).append('\n').append( tr::lng_gift_premium_label(tr::now) )); @@ -101,6 +99,18 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { data.cost, data.currency, true)); + if (const auto stars = data.stars) { + const auto starsText = QString::number(stars); + _byStars.setMarkedText( + st::giftBoxByStarsStyle, + tr::lng_gift_premium_by_stars( + tr::now, + lt_amount, + _delegate->ministar().append(' ' + starsText), + Ui::Text::WithEntities), + kMarkupTextOptions, + _delegate->textContext()); + } _userpic = nullptr; if (!_stars) { _stars.emplace(this, true, starsType); @@ -170,7 +180,9 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { QSize(buttonw, buttonh) ).marginsAdded(st::giftBoxButtonPadding); const auto skipy = _delegate->buttonSize().height() - - st::giftBoxButtonBottom + - (_byStars.isEmpty() + ? st::giftBoxButtonBottom + : st::giftBoxButtonBottomByStars) - inner.height(); const auto skipx = (width() - inner.width()) / 2; const auto outer = (width() - 2 * skipx); @@ -355,7 +367,9 @@ void GiftButton::paintEvent(QPaintEvent *e) { ? st::giftBoxSmallStickerTop : _text.isEmpty() ? st::giftBoxStickerStarTop - : st::giftBoxStickerTop), + : _byStars.isEmpty() + ? st::giftBoxStickerTop + : st::giftBoxStickerTopByStars), size.width(), size.height()), frame); @@ -367,7 +381,9 @@ void GiftButton::paintEvent(QPaintEvent *e) { ? st::giftBoxSmallStickerTop : _text.isEmpty() ? st::giftBoxStickerStarTop - : st::giftBoxStickerTop)); + : _byStars.isEmpty() + ? st::giftBoxStickerTop + : st::giftBoxStickerTopByStars)); _delegate->hiddenMark()->paint( p, frame, @@ -473,8 +489,9 @@ void GiftButton::paintEvent(QPaintEvent *e) { if (!_text.isEmpty()) { p.setPen(st::windowFg); _text.draw(p, { - .position = (position - + QPoint(0, st::giftBoxPremiumTextTop)), + .position = (position + QPoint(0, _byStars.isEmpty() + ? st::giftBoxPremiumTextTop + : st::giftBoxPremiumTextTopByStars)), .availableWidth = singlew, .align = style::al_top, }); @@ -492,6 +509,17 @@ void GiftButton::paintEvent(QPaintEvent *e) { + QPoint(padding.left(), padding.top())), .availableWidth = _price.maxWidth(), }); + + if (!_byStars.isEmpty()) { + p.setPen(st::creditsFg); + _byStars.draw(p, { + .position = QPoint( + position.x(), + _button.y() + _button.height() + st::giftBoxByStarsSkip), + .availableWidth = singlew, + .align = style::al_top, + }); + } } } @@ -515,6 +543,12 @@ TextWithEntities Delegate::star() { return owner->customEmojiManager().creditsEmoji(); } +TextWithEntities Delegate::ministar() { + const auto owner = &_window->session().data(); + const auto top = st::giftBoxByStarsStarTop; + return owner->customEmojiManager().ministarEmoji({ 0, top, 0, 0 }); +} + std::any Delegate::textContext() { return Core::MarkedTextContext{ .session = &_window->session(), diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index 8362ef85d..d7d829dab 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -102,6 +102,7 @@ enum class GiftButtonMode { class GiftButtonDelegate { public: [[nodiscard]] virtual TextWithEntities star() = 0; + [[nodiscard]] virtual TextWithEntities ministar() = 0; [[nodiscard]] virtual std::any textContext() = 0; [[nodiscard]] virtual QSize buttonSize() = 0; [[nodiscard]] virtual QMargins buttonExtend() = 0; @@ -144,6 +145,7 @@ private: GiftDescriptor _descriptor; Ui::Text::String _text; Ui::Text::String _price; + Ui::Text::String _byStars; std::shared_ptr _userpic; QImage _uniqueBackgroundCache; std::unique_ptr _uniquePatternEmoji; @@ -170,6 +172,7 @@ public: ~Delegate(); TextWithEntities star() override; + TextWithEntities ministar() override; std::any textContext() override; QSize buttonSize() override; QMargins buttonExtend() override; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 0925d8937..379036e6e 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -66,7 +66,6 @@ void CheckoutProcess::Start( Mode mode, Fn reactivate, Fn nonPanelPaymentFormProcess) { - const auto hasNonPanelPaymentFormProcess = !!nonPanelPaymentFormProcess; auto &processes = LookupSessionProcesses(&item->history()->session()); const auto media = item->media(); const auto invoice = media ? media->invoice() : nullptr; @@ -87,9 +86,7 @@ void CheckoutProcess::Start( i->second->setReactivateCallback(std::move(reactivate)); i->second->setNonPanelPaymentFormProcess( std::move(nonPanelPaymentFormProcess)); - if (!hasNonPanelPaymentFormProcess) { - i->second->requestActivate(); - } + i->second->requestActivate(); return; } const auto j = processes.byItem.emplace( @@ -100,9 +97,7 @@ void CheckoutProcess::Start( std::move(reactivate), std::move(nonPanelPaymentFormProcess), PrivateTag{})).first; - if (!hasNonPanelPaymentFormProcess) { - j->second->requestActivate(); - } + j->second->requestActivate(); } void CheckoutProcess::Start( @@ -110,16 +105,13 @@ void CheckoutProcess::Start( const QString &slug, Fn reactivate, Fn nonPanelPaymentFormProcess) { - const auto hasNonPanelPaymentFormProcess = !!nonPanelPaymentFormProcess; auto &processes = LookupSessionProcesses(session); const auto i = processes.bySlug.find(slug); if (i != end(processes.bySlug)) { i->second->setReactivateCallback(std::move(reactivate)); i->second->setNonPanelPaymentFormProcess( std::move(nonPanelPaymentFormProcess)); - if (!hasNonPanelPaymentFormProcess) { - i->second->requestActivate(); - } + i->second->requestActivate(); return; } const auto j = processes.bySlug.emplace( @@ -130,20 +122,21 @@ void CheckoutProcess::Start( std::move(reactivate), std::move(nonPanelPaymentFormProcess), PrivateTag{})).first; - if (!hasNonPanelPaymentFormProcess) { - j->second->requestActivate(); - } + j->second->requestActivate(); } void CheckoutProcess::Start( InvoicePremiumGiftCode giftCodeInvoice, - Fn reactivate) { + Fn reactivate, + Fn nonPanelPaymentFormProcess) { const auto randomId = giftCodeInvoice.randomId; auto id = InvoiceId{ std::move(giftCodeInvoice) }; auto &processes = LookupSessionProcesses(SessionFromId(id)); const auto i = processes.byRandomId.find(randomId); if (i != end(processes.byRandomId)) { i->second->setReactivateCallback(std::move(reactivate)); + i->second->setNonPanelPaymentFormProcess( + std::move(nonPanelPaymentFormProcess)); i->second->requestActivate(); return; } @@ -153,7 +146,7 @@ void CheckoutProcess::Start( std::move(id), Mode::Payment, std::move(reactivate), - nullptr, + std::move(nonPanelPaymentFormProcess), PrivateTag{})).first; j->second->requestActivate(); } @@ -372,7 +365,9 @@ void CheckoutProcess::setNonPanelPaymentFormProcess( } void CheckoutProcess::requestActivate() { - _panel->requestActivate(); + if (!_nonPanelPaymentFormProcess) { + _panel->requestActivate(); + } } not_null CheckoutProcess::panelDelegate() { diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 8131a4533..d5f6ce945 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -88,7 +88,8 @@ public: Fn nonPanelPaymentFormProcess); static void Start( InvoicePremiumGiftCode giftCodeInvoice, - Fn reactivate); + Fn reactivate, + Fn nonPanelPaymentFormProcess = nullptr); static void Start( InvoiceCredits creditsInvoice, Fn reactivate); diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 790b7a960..a08aa4f82 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -219,6 +219,13 @@ MTPinputStorePaymentPurpose InvoiceCreditsGiveawayToTL( MTP_int(invoice.users)); } +bool IsPremiumForStarsInvoice(const InvoiceId &id) { + const auto giftCode = std::get_if(&id.value); + return giftCode + && !giftCode->creditsAmount + && (giftCode->currency == ::Ui::kCreditsCurrency); +} + Form::Form(InvoiceId id, bool receipt) : _id(id) , _session(SessionFromId(id)) @@ -412,12 +419,29 @@ MTPInputInvoice Form::inputInvoice() const { MTP_long(giftCode.amount)); const auto users = std::get_if( &giftCode.purpose); - if (users) { + auto message = (users && !users->message.empty()) + ? MTP_textWithEntities( + MTP_string(users->message.text), + Api::EntitiesToMTP( + &users->users.front()->session(), + users->message.entities, + Api::ConvertOption::SkipLocal)) + : std::optional(); + if (users + && users->users.size() == 1 + && giftCode.currency == ::Ui::kCreditsCurrency) { + using Flag = MTPDinputInvoicePremiumGiftStars::Flag; + return MTP_inputInvoicePremiumGiftStars( + MTP_flags(message ? Flag::f_message : Flag()), + users->users.front()->inputUser, + MTP_int(giftCode.months), + message.value_or(MTPTextWithEntities())); + } else if (users) { using Flag = MTPDinputStorePaymentPremiumGiftCode::Flag; return MTP_inputInvoicePremiumGiftCode( MTP_inputStorePaymentPremiumGiftCode( MTP_flags((users->boostPeer ? Flag::f_boost_peer : Flag()) - | (users->message.empty() ? Flag(0) : Flag::f_message)), + | (message ? Flag::f_message : Flag())), MTP_vector_from_range(ranges::views::all( users->users ) | ranges::views::transform([](not_null user) { @@ -426,12 +450,7 @@ MTPInputInvoice Form::inputInvoice() const { users->boostPeer ? users->boostPeer->input : MTPInputPeer(), MTP_string(giftCode.currency), MTP_long(giftCode.amount), - MTP_textWithEntities( - MTP_string(users->message.text), - Api::EntitiesToMTP( - &users->users.front()->session(), - users->message.entities, - Api::ConvertOption::SkipLocal))), + message.value_or(MTPTextWithEntities())), option); } else { return MTP_inputInvoicePremiumGiftCode( diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 8dae0d008..8fa322c72 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -287,6 +287,8 @@ struct FormUpdate : std::variant< [[nodiscard]] MTPinputStorePaymentPurpose InvoiceCreditsGiveawayToTL( const InvoicePremiumGiftCode &invoice); +[[nodiscard]] bool IsPremiumForStarsInvoice(const InvoiceId &id); + class Form final : public base::has_weak_ptr { public: Form(InvoiceId id, bool receipt); diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index c76430820..d63b505a9 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -57,7 +57,8 @@ void ProcessCreditsPayment( onstack(CheckoutResult::Cancelled); } return; - } else if (form->starGiftForm) { + } else if (form->starGiftForm + || IsPremiumForStarsInvoice(form->id)) { const auto done = [=](std::optional error) { const auto onstack = maybeReturnToBot; if (error) { @@ -86,7 +87,7 @@ void ProcessCreditsPayment( onstack(CheckoutResult::Paid); } }; - Ui::SendStarGift(&show->session(), form, done); + Ui::SendStarsForm(&show->session(), form, done); return; } const auto unsuccessful = std::make_shared(true); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 0ac5c025a..f492a6e5e 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -429,8 +429,7 @@ void Credits::setupContent() { Ui::AddSkip(content); struct State final { - rpl::variable confirmButtonBusy = false; - std::optional api; + BuyStarsHandler buyStars; }; const auto state = content->lifetime().make_state(); @@ -438,60 +437,21 @@ void Credits::setupContent() { object_ptr( content, rpl::conditional( - state->confirmButtonBusy.value(), + state->buyStars.loadingValue(), rpl::single(QString()), tr::lng_credits_buy_button()), st::creditsSettingsBigBalanceButton), st::boxRowPadding); button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); const auto show = _controller->uiShow(); - const auto optionsBox = [=](not_null box) { - box->setStyle(st::giveawayGiftCodeBox); - box->setWidth(st::boxWideWidth); - box->setTitle(tr::lng_credits_summary_options_subtitle()); - const auto inner = box->verticalLayout(); - const auto self = show->session().user(); - const auto options = state->api - ? state->api->options() - : Data::CreditTopupOptions(); - const auto amount = StarsAmount(); - FillCreditOptions(show, inner, self, amount, paid, nullptr, options); - - const auto button = box->addButton(tr::lng_close(), [=] { - box->closeBox(); - }); - const auto buttonWidth = st::boxWideWidth - - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); - button->widthValue() | rpl::filter([=] { - return (button->widthNoMargins() != buttonWidth); - }) | rpl::start_with_next([=] { - button->resizeToWidth(buttonWidth); - }, button->lifetime()); - }; - button->setClickedCallback([=] { - if (state->api && !state->api->options().empty()) { - state->confirmButtonBusy = false; - show->show(Box(optionsBox)); - } else { - state->confirmButtonBusy = true; - state->api.emplace(show->session().user()); - state->api->request( - ) | rpl::start_with_error_done([=](const QString &error) { - state->confirmButtonBusy = false; - show->showToast(error); - }, [=] { - state->confirmButtonBusy = false; - show->show(Box(optionsBox)); - }, content->lifetime()); - } - }); + button->setClickedCallback(state->buyStars.handler(show, paid)); { using namespace Info::Statistics; const auto loadingAnimation = InfiniteRadialAnimationWidget( button, button->height() / 2); AddChildToWidgetCenter(button, loadingAnimation); - loadingAnimation->showOn(state->confirmButtonBusy.value()); + loadingAnimation->showOn(state->buyStars.loadingValue()); } const auto paddings = rect::m::sum::h(st::boxRowPadding); button->widthValue() | rpl::filter([=] { @@ -699,4 +659,62 @@ Type CreditsId() { return Credits::Id(); } +Fn BuyStarsHandler::handler( + std::shared_ptr<::Main::SessionShow> show, + Fn paid) { + const auto optionsBox = [=](not_null box) { + box->setStyle(st::giveawayGiftCodeBox); + box->setWidth(st::boxWideWidth); + box->setTitle(tr::lng_credits_summary_options_subtitle()); + const auto inner = box->verticalLayout(); + const auto self = show->session().user(); + const auto options = _api + ? _api->options() + : Data::CreditTopupOptions(); + const auto amount = StarsAmount(); + const auto weak = Ui::MakeWeak(box); + FillCreditOptions(show, inner, self, amount, [=] { + if (const auto strong = weak.data()) { + strong->closeBox(); + } + if (const auto onstack = paid) { + onstack(); + } + }, nullptr, options); + + const auto button = box->addButton(tr::lng_close(), [=] { + box->closeBox(); + }); + const auto buttonWidth = st::boxWideWidth + - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::start_with_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); + }; + return crl::guard(this, [=] { + if (_api && !_api->options().empty()) { + _loading = false; + show->show(Box(crl::guard(this, optionsBox))); + } else { + _loading = true; + const auto user = show->session().user(); + _api = std::make_unique(user); + _api->request( + ) | rpl::start_with_error_done([=](const QString &error) { + _loading = false; + show->showToast(error); + }, [=] { + _loading = false; + show->show(Box(crl::guard(this, optionsBox))); + }, _lifetime); + } + }); +} + +rpl::producer BuyStarsHandler::loadingValue() const { + return _loading.value(); +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits.h b/Telegram/SourceFiles/settings/settings_credits.h index c0e32e1be..480ced9e6 100644 --- a/Telegram/SourceFiles/settings/settings_credits.h +++ b/Telegram/SourceFiles/settings/settings_credits.h @@ -9,9 +9,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_type.h" +namespace Api { +class CreditsTopupOptions; +} // namespace Api + +namespace Main { +class SessionShow; +} // namespace Main + namespace Settings { [[nodiscard]] Type CreditsId(); -} // namespace Settings +class BuyStarsHandler final : public base::has_weak_ptr { +public: + [[nodiscard]] Fn handler( + std::shared_ptr<::Main::SessionShow> show, + Fn paid = nullptr); + [[nodiscard]] rpl::producer loadingValue() const; +private: + std::unique_ptr _api; + rpl::variable _loading; + rpl::lifetime _lifetime; + +}; + +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 62075809f..c6f3d15d5 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -569,8 +569,8 @@ void FillCreditOptions( if (const auto strong = weak.data()) { strong->window()->setFocus(); if (result == Payments::CheckoutResult::Paid) { - if (paid) { - paid(); + if (const auto onstack = paid) { + onstack(); } } } diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index 8c692403b..67c74b81f 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" +#include "styles/style_credits.h" namespace Ui { namespace { @@ -54,7 +55,7 @@ void SendButton::setState(State state) { || _state.starsToSend != state.starsToSend) { _starsToSendText.setMarkedText( _st.stars.style, - Text::IconEmoji(&st::boxStarIconEmoji).append( + Text::IconEmoji(&st::starIconEmoji).append( Lang::FormatCountToShort(state.starsToSend).string), kMarkupTextOptions); } diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 2132c79f6..8add6b1dc 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -63,11 +63,12 @@ creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) { } starIconEmoji: IconEmoji { - icon: icon{{ "payments/small_star", windowFg }}; - padding: margins(0px, -3px, 0px, 0px); + icon: icon{{ "payments/premium_emoji", creditsBg1 }}; + padding: margins(4px, 1px, 4px, 0px); +} +starIconEmojiColored: IconEmoji(starIconEmoji) { + useIconColor: true; } -starIconSmall: icon{{ "payments/small_star", windowFg }}; -starIconSmallPadding: margins(0px, -3px, 0px, 0px); creditsHistoryEntryTypeAds: icon {{ "folders/folders_channels", premiumButtonFg }}; @@ -123,10 +124,17 @@ giftBoxGiftHeight: 164px; giftBoxGiftSmall: 108px; giftBoxGiftRadius: 12px; giftBoxGiftBadgeFont: font(10px semibold); +giftBoxByStarsStyle: TextStyle(defaultTextStyle) { + font: font(10px); +} +giftBoxByStarsSkip: 2px; +giftBoxByStarsStarTop: 3px; giftBoxPremiumIconSize: 64px; giftBoxPremiumIconTop: 10px; giftBoxPremiumTextTop: 84px; +giftBoxPremiumTextTopByStars: 78px; giftBoxButtonBottom: 12px; +giftBoxButtonBottomByStars: 18px; giftBoxButtonPadding: margins(8px, 4px, 8px, 4px); giftBoxPreviewStickerPadding: margins(10px, 12px, 10px, 16px); giftBoxPreviewTitlePadding: margins(12px, 4px, 12px, 4px); @@ -135,6 +143,7 @@ giftBoxButtonMargin: margins(12px, 8px, 12px, 12px); giftBoxStickerTop: 0px; giftBoxStickerStarTop: 24px; giftBoxSmallStickerTop: 16px; +giftBoxStickerTopByStars: -4px; giftBoxStickerSize: size(80px, 80px); giftBoxUserpicSize: 24px; giftBoxUserpicSkip: 2px; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index da0d634ec..76f254814 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit da0d634ec373d8ba0b8f66f3381a87e43edffdf2 +Subproject commit 76f25481470faee2e0a24aa7be8c6d58564addea