From f06f6541919e14b92b43b5f367e72ea08ab2aa93 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Nov 2025 18:42:56 +0400 Subject: [PATCH] Confirm making an offer. --- Telegram/Resources/langs/lang.strings | 7 ++ Telegram/SourceFiles/boxes/star_gift_box.cpp | 104 +++++++++++++++++- .../controls/history_view_suggest_options.cpp | 21 ++-- .../media/history_view_theme_document.cpp | 17 ++- .../settings/settings_credits_graphics.cpp | 4 + .../settings/settings_credits_graphics.h | 3 + Telegram/SourceFiles/ui/effects/credits.style | 6 + 7 files changed, 143 insertions(+), 19 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a6153979b0..510c8daf6c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2353,6 +2353,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_gift_offer" = "{user} offered you {cost} for {name}."; "lng_action_gift_offer_you" = "You offered {cost} for {name}."; "lng_action_gift_offer_state_expires" = "This offer expires in {time}."; +"lng_action_gift_offer_time_large" = "{hours} h"; "lng_action_gift_offer_time_medium" = "{hours} h {minutes} m"; "lng_action_gift_offer_time_small" = "{minutes} m"; "lng_action_gift_offer_state_accepted" = "This offer was accepted."; @@ -3137,6 +3138,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}."; "lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages."; "lng_credits_small_balance_for_suggest" = "Buy **Stars** to suggest post to {channel}."; +"lng_credits_small_balance_for_offer" = "Buy **Stars** to offer for this gift."; "lng_credits_small_balance_for_search" = "Buy **Stars** to search through public posts."; "lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram."; "lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; @@ -3966,6 +3968,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_offer_you_get" = "You will receive {cost} after fees."; "lng_gift_offer_value_higher" = "{percent} higher"; "lng_gift_offer_sell_for" = "Sell for {price}"; +"lng_gift_offer_confirm_title" = "Confirm Offer"; +"lng_gift_offer_confirm_text" = "Do you want to offer {cost} to {user} for {name}?"; +"lng_gift_offer_table_offer" = "Offer"; +"lng_gift_offer_table_fee" = "Fee"; +"lng_gift_offer_table_duration" = "Duration"; "lng_gift_sell_unlist_title" = "Unlist {name}"; "lng_gift_sell_unlist_sure" = "Are you sure you want to unlist your gift?"; "lng_gift_sell_title" = "Price in Stars"; diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 608464f68d..8a370afb95 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -3564,31 +3564,122 @@ void SendOfferBuyGift( std::shared_ptr show, std::shared_ptr unique, SuggestOptions options, - Fn done) { + int starsPerMessage, + Fn done) { const auto randomId = base::RandomValue(); const auto owner = show->session().data().peer(unique->ownerId); using Flag = MTPpayments_SendStarGiftOffer::Flag; show->session().api().request(MTPpayments_SendStarGiftOffer( - MTP_flags(Flag() | Flag()),//Flag::f_allow_paid_stars) + MTP_flags(starsPerMessage ? Flag::f_allow_paid_stars : Flag()), owner->input, MTP_string(unique->slug), StarsAmountToTL(options.price()), MTP_int(options.offerDuration), MTP_long(randomId), - MTP_long(0) // allow_paid_stars + MTP_long(starsPerMessage) )).done([=](const MTPUpdates &result) { show->session().api().applyUpdates(result); - done(); + done(true); }).fail([=](const MTP::Error &error) { if (error.type() == u""_q) { - } else { show->showToast(error.type()); } + done(false); }).send(); } +void ConfirmOfferBuyGift( + std::shared_ptr show, + std::shared_ptr unique, + SuggestOptions options, + Fn done) { + const auto owner = show->session().data().peer(unique->ownerId); + const auto fee = owner->starsPerMessageChecked(); + const auto price = options.price(); + const auto sent = std::make_shared(); + const auto send = [=](Fn close) { + if (*sent) { + return; + } + *sent = true; + SendOfferBuyGift(show, unique, options, fee, [=](bool ok) { + *sent = false; + if (ok) { + if (const auto window = show->resolveWindow()) { + window->showPeerHistory(owner->id); + } + done(); + close(); + } + }); + }; + + show->show(Box([=](not_null box) { + Ui::ConfirmBox(box, { + .text = tr::lng_gift_offer_confirm_text( + tr::now, + lt_cost, + tr::bold(PrepareCreditsAmountText(options.price())), + lt_user, + tr::bold(owner->shortName()), + lt_name, + tr::bold(Data::UniqueGiftName(*unique)), + tr::marked), + .confirmed = send, + .confirmText = tr::lng_payments_pay_amount( + tr::now, + lt_amount, + Ui::Text::IconEmoji(price.ton() + ? &st::buttonTonIconEmoji + : &st::buttonStarIconEmoji + ).append(Lang::FormatCreditsAmountDecimal(price.ton() + ? price + : CreditsAmount(price.whole() + fee))), + tr::marked), + .title = tr::lng_gift_offer_confirm_title(), + }); + + auto helper = Ui::Text::CustomEmojiHelper(); + const auto starIcon = helper.paletteDependent( + Ui::Earn::IconCreditsEmoji()); + const auto tonIcon = helper.paletteDependent( + Ui::Earn::IconCurrencyEmoji()); + const auto context = helper.context(); + const auto table = box->addRow( + object_ptr(box, st::defaultTable), + st::boxPadding); + const auto add = [&](tr::phrase<> label, TextWithEntities value) { + table->addRow( + object_ptr( + table, + label(), + st::defaultTable.defaultLabel), + object_ptr( + table, + rpl::single(value), + st::defaultTable.defaultValue, + st::defaultPopupMenu, + context), + st::giveawayGiftCodeLabelMargin, + st::giveawayGiftCodeValueMargin); + }; + add(tr::lng_gift_offer_table_offer, tr::marked(price.ton() + ? tonIcon + : starIcon).append(Lang::FormatCreditsAmountDecimal(price))); + if (fee) { + add(tr::lng_gift_offer_table_fee, tr::marked(starIcon).append( + Lang::FormatCreditsAmountDecimal(CreditsAmount(fee)))); + } + const auto hours = options.offerDuration / 3600; + const auto duration = hours + ? tr::lng_hours(tr::now, lt_count, hours) + : tr::lng_minutes(tr::now, lt_count, options.offerDuration / 60); + add(tr::lng_gift_offer_table_duration, tr::marked(duration)); + })); +} + void ShowOfferBuyBox( std::shared_ptr show, std::shared_ptr unique) { @@ -3596,7 +3687,7 @@ void ShowOfferBuyBox( const auto weak = std::make_shared>(); const auto done = [=](SuggestOptions result) { - SendOfferBuyGift(show, unique, result, [=] { + ConfirmOfferBuyGift(show, unique, result, [=] { if (const auto strong = weak->get()) { strong->closeBox(); } @@ -3612,6 +3703,7 @@ void ShowOfferBuyBox( .done = done, .value = options, .mode = SuggestMode::Gift, + .giftName = UniqueGiftName(*unique), }); *weak = priceBox.data(); show->show(std::move(priceBox)); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp index e3cdbbe279..cb335b042b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "info/channel_statistics/earn/earn_format.h" #include "info/channel_statistics/earn/earn_icons.h" #include "lang/lang_keys.h" @@ -415,6 +416,8 @@ void ChooseSuggestPriceBox( state->price = args.value.price(); const auto peer = args.peer; + [[maybe_unused]] const auto details = ComputePaymentDetails(peer, 1); + const auto mode = args.mode; const auto admin = peer->amMonoforumAdmin(); const auto broadcast = peer->monoforumBroadcast(); @@ -610,7 +613,7 @@ void ChooseSuggestPriceBox( Ui::AddSkip(container); - if (args.mode == SuggestMode::Gift) { + if (mode == SuggestMode::Gift) { const auto day = 86400; auto durations = std::vector{ day / 4, @@ -715,14 +718,15 @@ void ChooseSuggestPriceBox( box->uiShow()->show(Box(InsufficientTonBox, usePeer, value)); return; } - } else if (!admin) { + } + const auto requiredStars = peer->starsPerMessageChecked() + + (ton ? 0 : int(base::SafeRound(value.value()))); + if (!admin && requiredStars) { if (!credits->loaded()) { state->savePending = true; return; } - const auto required = peer->starsPerMessageChecked() - + int(base::SafeRound(value.value())); - if (credits->balance() < CreditsAmount(required)) { + if (credits->balance() < CreditsAmount(requiredStars)) { using namespace Settings; const auto done = [=](SmallBalanceResult result) { if (result == SmallBalanceResult::Success @@ -730,10 +734,13 @@ void ChooseSuggestPriceBox( state->save(); } }; + const auto source = (mode == SuggestMode::Gift) + ? Settings::SmallBalanceSource(SmallBalanceForOffer()) + : SmallBalanceForSuggest{ usePeer->id }; MaybeRequestBalanceIncrease( Main::MakeSessionShow(box->uiShow(), session), - required, - SmallBalanceForSuggest{ usePeer->id }, + requiredStars, + source, done); return; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp index bb1f81b017..1b003d387e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -690,12 +690,17 @@ TextWithEntities GiftServiceBox::subtitle() { if (left >= 3600) { const auto hours = left / 3600; const auto minutes = (left % 3600) / 60; - time = tr::lng_action_gift_offer_time_medium( - tr::now, - lt_hours, - QString::number(hours), - lt_minutes, - QString::number(minutes)); + time = minutes + ? tr::lng_action_gift_offer_time_medium( + tr::now, + lt_hours, + QString::number(hours), + lt_minutes, + QString::number(minutes)) + : tr::lng_action_gift_offer_time_large( + tr::now, + lt_hours, + QString::number(hours)); } else { const auto minutes = left / 60; time = tr::lng_action_gift_offer_time_small( diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 396433fbef..a0ad121040 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -2934,6 +2934,8 @@ void SmallBalanceBox( return value.recipientId ? owner->peer(value.recipientId)->shortName() : QString(); + }, [](SmallBalanceForOffer) { + return QString(); }, [](SmallBalanceForSearch) { return QString(); }); @@ -2994,6 +2996,8 @@ void SmallBalanceBox( lt_channel, rpl::single(Ui::Text::Bold(name)), Ui::Text::RichLangValue) + : v::is(source) + ? tr::lng_credits_small_balance_for_offer(tr::rich) : v::is(source) ? tr::lng_credits_small_balance_for_search( Ui::Text::RichLangValue) diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 2b7d35d69d..0248a676e4 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -253,6 +253,8 @@ struct SmallBalanceForMessage { struct SmallBalanceForSuggest { PeerId recipientId; }; +struct SmallBalanceForOffer { +}; struct SmallBalanceForSearch { }; struct SmallBalanceSource : std::variant< @@ -264,6 +266,7 @@ struct SmallBalanceSource : std::variant< SmallBalanceStarGift, SmallBalanceForMessage, SmallBalanceForSuggest, + SmallBalanceForOffer, SmallBalanceForSearch> { using variant::variant; }; diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 30dfd0c092..c288edba75 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -92,6 +92,9 @@ starIconEmojiLarge: IconEmoji { starIconEmojiInline: IconEmoji(starIconEmojiSmall) { padding: margins(0px, 3px, 0px, 0px); } +buttonStarIconEmoji: IconEmoji(starIconEmoji) { + padding: margins(4px, 2px, 4px, 0px); +} tonIconEmoji: IconEmoji { icon: icon{{ "chat/mini_ton_bold", currencyFg }}; @@ -104,6 +107,9 @@ tonIconEmojiLarge: IconEmoji { tonIconEmojiInSmall: IconEmoji(tonIconEmoji) { padding: margins(0px, 2px, 0px, 0px); } +buttonTonIconEmoji: IconEmoji(tonIconEmoji) { + padding: margins(1px, 3px, 1px, 0px); +} creditsHistoryEntryTypeAds: icon {{ "folders/folders_channels", premiumButtonFg }}; creditsHistorySearchPostsIcon: icon {{ "box_search", historyPeerUserpicFg }};