Confirm making an offer.

This commit is contained in:
John Preston
2025-11-28 18:42:56 +04:00
parent 356d20542e
commit f06f654191
7 changed files with 143 additions and 19 deletions

View File

@@ -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";

View File

@@ -3564,31 +3564,122 @@ void SendOfferBuyGift(
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Data::UniqueGift> unique,
SuggestOptions options,
Fn<void()> done) {
int starsPerMessage,
Fn<void(bool)> done) {
const auto randomId = base::RandomValue<uint64>();
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<ChatHelpers::Show> show,
std::shared_ptr<Data::UniqueGift> unique,
SuggestOptions options,
Fn<void()> 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<bool>();
const auto send = [=](Fn<void()> 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<Ui::GenericBox*> 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<Ui::TableLayout>(box, st::defaultTable),
st::boxPadding);
const auto add = [&](tr::phrase<> label, TextWithEntities value) {
table->addRow(
object_ptr<Ui::FlatLabel>(
table,
label(),
st::defaultTable.defaultLabel),
object_ptr<Ui::FlatLabel>(
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<ChatHelpers::Show> show,
std::shared_ptr<Data::UniqueGift> unique) {
@@ -3596,7 +3687,7 @@ void ShowOfferBuyBox(
const auto weak = std::make_shared<base::weak_qptr<Ui::BoxContent>>();
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));

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -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<SmallBalanceForOffer>(source)
? tr::lng_credits_small_balance_for_offer(tr::rich)
: v::is<SmallBalanceForSearch>(source)
? tr::lng_credits_small_balance_for_search(
Ui::Text::RichLangValue)

View File

@@ -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;
};

View File

@@ -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 }};