Allow pay for upgrade when sending.

This commit is contained in:
John Preston 2024-12-27 21:32:25 +04:00
parent 42c350243a
commit 5df632264f
15 changed files with 445 additions and 198 deletions

View file

@ -2031,11 +2031,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_got_subtitle" = "Gift from {user}";
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
"lng_action_gift_got_upgradable_text" = "Upgrade this gift to a unique collectible.";
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
"lng_action_gift_sent_subtitle" = "Gift for {user}";
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
"lng_action_gift_sent_upgradable" = "{user} can upgrade this gift to a unique collectible.";
"lng_action_gift_premium_months#one" = "{count} Month Premium";
"lng_action_gift_premium_months#other" = "{count} Months Premium";
"lng_action_gift_premium_about" = "Subscription for exclusive Telegram features.";
@ -3227,6 +3229,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_send_message" = "Enter Message";
"lng_gift_send_anonymous" = "Hide My Name";
"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_unique" = "Make Unique for {price}";
"lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
"lng_gift_send_unique_link" = "Learn More >";
"lng_gift_send_premium_about" = "Only {user} will see your message.";
"lng_gift_send_button" = "Send a Gift for {cost}";
"lng_gift_sent_title" = "Gift Sent!";
@ -3235,6 +3240,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_price_unique" = "Unique";
"lng_gift_view_unpack" = "Unpack";
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
"lng_gift_hidden_hint" = "This gift is hidden. Only you can see it.";
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
@ -3286,6 +3292,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_sell_small#other" = "sell for {count} Stars";
"lng_gift_upgrade_title" = "Upgrade Gift";
"lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction.";
"lng_gift_upgrade_preview_title" = "Make Unique";
"lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible.";
"lng_gift_upgrade_unique_title" = "Unique";
"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift.";
"lng_gift_upgrade_transferable_title" = "Transferable";
@ -3293,7 +3301,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_upgrade_tradable_title" = "Tradable";
"lng_gift_upgrade_tradable_about" = "Sell or auction your gift on third-party NFT marketplaces.";
"lng_gift_upgrade_button" = "Upgrade for {price}";
"lng_gift_upgrade_add_sender" = "Add sender's name";
"lng_gift_upgrade_free" = "Upgrade for Free";
"lng_gift_upgrade_confirm" = "Confirm";
"lng_gift_upgrade_add_my" = "Add my name to the gift";
"lng_gift_upgrade_add_sender" = "Add sender's name to the gift";
"lng_gift_upgrade_add_comment" = "Add sender's name and comment";
"lng_gift_upgraded_title" = "Gift Upgraded";
"lng_gift_upgraded_about" = "Your gift {name} now has unique attributes and can be transferred to others";

View file

@ -130,7 +130,8 @@ constexpr auto kRarityTooltipDuration = 3 * crl::time(1000);
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
PeerId id,
bool withSendGiftButton = false) {
rpl::producer<QString> button = nullptr,
Fn<void()> handler = nullptr) {
auto result = object_ptr<Ui::AbstractButton>(parent);
const auto raw = result.data();
@ -141,19 +142,17 @@ constexpr auto kRarityTooltipDuration = 3 * crl::time(1000);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
withSendGiftButton ? peer->shortName() : peer->name(),
(button && handler) ? peer->shortName() : peer->name(),
st::giveawayGiftCodeValue);
const auto send = withSendGiftButton
const auto send = (button && handler)
? Ui::CreateChild<Ui::RoundButton>(
raw,
tr::lng_gift_send_small(),
std::move(button),
st::starGiftSmallButton)
: nullptr;
if (send) {
send->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
send->setClickedCallback([=] {
Ui::ShowStarGiftBox(controller->parentController(), peer);
});
send->setClickedCallback(std::move(handler));
}
rpl::combine(
raw->widthValue(),
@ -1204,19 +1203,25 @@ void AddStarGiftTable(
const auto session = &controller->session();
const auto unique = entry.uniqueGift.get();
if (unique) {
const auto ownerId = PeerId(entry.bareGiftOwnerId);
auto send = entry.in ? tr::lng_gift_unique_owner_change() : nullptr;
auto handler = entry.in ? Fn<void()>([=] {}) : nullptr;
AddTableRow(
table,
tr::lng_gift_unique_owner(),
MakePeerTableValue(table, controller, peerId, false),
MakePeerTableValue(table, controller, ownerId, send, handler),
st::giveawayGiftCodePeerMargin);
//"lng_gift_unique_owner_change" = "change";
} else if (peerId) {
const auto user = session->data().peer(peerId)->asUser();
const auto withSendButton = entry.in && user && !user->isBot();
auto send = withSendButton ? tr::lng_gift_send_small() : nullptr;
auto handler = send ? Fn<void()>([=] {
Ui::ShowStarGiftBox(controller->parentController(), user);
}) : nullptr;
AddTableRow(
table,
tr::lng_credits_box_history_entry_peer_in(),
MakePeerTableValue(table, controller, peerId, withSendButton),
MakePeerTableValue(table, controller, peerId, send, handler),
st::giveawayGiftCodePeerMargin);
} else if (!entry.soldOutInfo) {
AddTableRow(
@ -1390,6 +1395,12 @@ void AddStarGiftTable(
: nullptr;
const auto date = base::unixtime::parse(original.date).date();
const auto dateText = TextWithEntities{ langDayOfMonth(date) };
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = std::move(update),
};
};
auto label = object_ptr<Ui::FlatLabel>(
table,
(from
@ -1427,7 +1438,9 @@ void AddStarGiftTable(
lt_text,
rpl::single(original.message),
Ui::Text::WithEntities))),
st::giveawayGiftMessage);
st::giveawayGiftMessage,
st::defaultPopupMenu,
makeContext);
const auto showBoxLink = [=](not_null<PeerData*> peer) {
return std::make_shared<LambdaClickHandler>([=] {
controller->uiShow()->showBox(

View file

@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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"
@ -67,6 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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/shadow.h"
#include "window/themes/window_theme.h"
#include "window/section_widget.h"
@ -92,6 +94,7 @@ 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);
using namespace HistoryView;
using namespace Info::PeerGifts;
@ -111,6 +114,7 @@ struct GiftDetails {
TextWithEntities text;
uint64 randomId = 0;
bool anonymous = false;
bool upgraded = false;
};
class PreviewDelegate final : public DefaultElementDelegate {
@ -250,11 +254,15 @@ auto GenerateGiftMedia(
tr::now,
Text::RichLangValue);
}, [&](const GiftTypeStars &gift) {
return tr::lng_action_gift_got_stars_text(
tr::now,
lt_count,
gift.info.starsConverted,
Text::RichLangValue);
return data.upgraded
? tr::lng_action_gift_got_upgradable_text(
tr::now,
Text::RichLangValue)
: tr::lng_action_gift_got_stars_text(
tr::now,
lt_count,
gift.info.starsConverted,
Text::RichLangValue);
});
auto description = data.text.empty()
? std::move(textFallback)
@ -268,6 +276,14 @@ auto GenerateGiftMedia(
.session = &parent->history()->session(),
.customEmojiRepaint = [parent] { parent->repaint(); },
});
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));
};
}
@ -401,7 +417,8 @@ PreviewWrap::PreviewWrap(
void ShowSentToast(
not_null<Window::SessionController*> window,
const GiftDescriptor &descriptor) {
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;
@ -414,10 +431,12 @@ void ShowSentToast(
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,
gift.info.stars,
amount,
Text::RichLangValue);
});
const auto strong = window->showToast({
@ -475,7 +494,8 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
return FillAmountAndCurrency(data.cost, data.currency, true);
}, [&](GiftTypeStars data) {
const auto stars = data.info.stars;
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();
@ -1063,6 +1083,7 @@ void SendGift(
.user = peer->asUser(),
.limitedCount = gift.info.limitedCount,
.anonymous = details.anonymous,
.upgraded = details.upgraded,
}, done, processNonPanelPaymentFormFactory);
});
}
@ -1090,6 +1111,24 @@ void SendGift(
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 SendUpgradeRequest(
not_null<Window::SessionController*> controller,
Settings::SmallBalanceResult result,
@ -1108,17 +1147,7 @@ void SendUpgradeRequest(
)).done([=](const MTPpayments_PaymentResult &result) {
result.match([&](const MTPDpayments_paymentResult &data) {
session->api().applyUpdates(data.vupdates());
const auto gift = FindUniqueGift(session, data.vupdates());
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),
});
}
ShowGiftUpgradedToast(weak, session, data.vupdates());
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
});
done(Payments::CheckoutResult::Paid);
@ -1150,17 +1179,8 @@ void UpgradeGift(
MTP_int(messageId.bare)
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(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),
});
}
ShowGiftUpgradedToast(weak, session, result);
done(Payments::CheckoutResult::Paid);
}).fail([=](const MTP::Error &error) {
if (const auto strong = weak.get()) {
strong->showToast(error.type());
@ -1233,6 +1253,69 @@ void SoldOutBox(
Data::SubscriptionEntry());
}
void AddUpgradeButton(
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session,
int cost,
QString name,
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());
const auto makeContext = [session](Fn<void()> update) {
return Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = std::move(update),
};
};
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,
std::move(makeContext));
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,
tr::lng_gift_send_unique_about(
lt_user,
rpl::single(TextWithEntities{ name }),
lt_link,
tr::lng_gift_send_unique_link() | Text::ToLink(),
Text::WithEntities));
about->setClickHandlerFilter([=](const auto &...) {
preview();
return false;
});
}
void SendGiftBox(
not_null<GenericBox*> box,
not_null<Window::SessionController*> window,
@ -1247,20 +1330,6 @@ void SendGiftBox(
});
const auto session = &window->session();
auto cost = rpl::single([&] {
return v::match(descriptor, [&](const GiftTypePremium &data) {
if (data.currency == kCreditsCurrency) {
return CreditsEmojiSmall(session).append(
Lang::FormatCountDecimal(std::abs(data.cost)));
}
return TextWithEntities{
FillAmountAndCurrency(data.cost, data.currency),
};
}, [&](const GiftTypeStars &data) {
return CreditsEmojiSmall(session).append(
Lang::FormatCountDecimal(std::abs(data.info.stars)));
});
}());
struct State {
rpl::variable<GiftDetails> details;
@ -1272,7 +1341,26 @@ void SendGiftBox(
.descriptor = descriptor,
.randomId = base::RandomValue<uint64>(),
};
const auto document = LookupGiftSticker(&window->session(), descriptor);
auto cost = state->details.value(
) | rpl::map([session](const GiftDetails &details) {
return v::match(details.descriptor, [&](const GiftTypePremium &data) {
if (data.currency == kCreditsCurrency) {
return CreditsEmojiSmall(session).append(
Lang::FormatCountDecimal(std::abs(data.cost)));
}
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();
}
@ -1283,7 +1371,7 @@ void SendGiftBox(
session,
state->details.value()));
const auto limit = StarGiftMessageLimit(&window->session());
const auto limit = StarGiftMessageLimit(session);
const auto text = AddPartInput(
window,
container,
@ -1309,7 +1397,7 @@ void SendGiftBox(
return true;
};
InitMessageFieldHandlers({
.session = &window->session(),
.session = session,
.show = window->uiShow(),
.field = text,
.customEmojiPaused = [=] {
@ -1328,11 +1416,39 @@ void SendGiftBox(
Emoji::SuggestionsController::Init(
box->getDelegate()->outerContainer(),
text,
&window->session(),
session,
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
if (v::is<GiftTypeStars>(descriptor)) {
AddDivider(container);
if (const auto stars = std::get_if<GiftTypeStars>(&descriptor)) {
if (const auto cost = stars->info.starsToUpgrade; cost > 0) {
const auto user = peer->asUser();
Assert(user != nullptr);
const auto id = stars->info.id;
const auto name = user->shortName();
const auto showing = std::make_shared<bool>();
AddDivider(container);
AddSkip(container);
AddUpgradeButton(container, session, cost, name, [=](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; },
.user = user,
.cost = int(cost),
});
});
} else {
AddDivider(container);
}
AddSkip(container);
container->add(
object_ptr<SettingsButton>(
@ -1373,7 +1489,7 @@ void SendGiftBox(
if (result == Payments::CheckoutResult::Paid) {
const auto copy = state->media;
window->showPeerHistory(peer);
ShowSentToast(window, descriptor);
ShowSentToast(window, descriptor, details);
}
if (const auto strong = weak.data()) {
box->closeBox();
@ -1894,13 +2010,10 @@ void AddUniqueGiftCover(
}, cover->lifetime());
}
struct UpgradeArgs {
struct UpgradeArgs : StarGiftUpgradeArgs {
std::vector<Data::UniqueGiftModel> models;
std::vector<Data::UniqueGiftPattern> patterns;
std::vector<Data::UniqueGiftBackdrop> backdrops;
not_null<UserData*> user;
MsgId itemId = 0;
int stars = 0;
};
[[nodiscard]] rpl::producer<Data::UniqueGift> MakeUpgradeGiftStream(
@ -1925,22 +2038,31 @@ struct UpgradeArgs {
const auto put = [=] {
const auto index = [](std::vector<int> &indices, const auto &v) {
if (indices.empty()) {
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());
}
const auto index = base::RandomIndex(indices.size());
const auto i = begin(indices) + index;
const auto result = *i;
indices.erase(i);
return result;
};
auto &models = state->data.models;
auto &patterns = state->data.patterns;
auto &backdrops = state->data.backdrops;
consumer.put_next(Data::UniqueGift{
.title = tr::lng_gift_upgrade_title(tr::now),
.title = (state->data.itemId
? 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)],
@ -1962,7 +2084,11 @@ void AddUpgradeGiftCover(
AddUniqueGiftCover(
container,
MakeUpgradeGiftStream(args),
tr::lng_gift_upgrade_about());
(args.itemId
? tr::lng_gift_upgrade_about()
: tr::lng_gift_upgrade_preview_about(
lt_name,
rpl::single(args.user->shortName()))));
}
void UpgradeBox(
@ -2025,45 +2151,75 @@ void UpgradeBox(
&st::menuIconReplace,
true);
container->add(
object_ptr<PlainShadow>(container),
st::boxRowPadding + QMargins(0, st::defaultVerticalListSkip, 0, 0));
box->setStyle(st::giftBox);
struct State {
bool sent = false;
bool preserveDetails = false;
};
const auto stars = args.stars;
const auto session = &controller->session();
const auto state = std::make_shared<State>();
const auto button = box->addButton(rpl::single(QString()), [=] {
if (state->sent) {
const auto preview = !args.itemId;
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)
: tr::lng_gift_upgrade_add_my(tr::now))),
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 = 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 if (const auto strong = weak.data()) {
strong->closeBox();
} else {
controller->showPeerHistory(args.user);
if (const auto strong = weak.data()) {
strong->closeBox();
}
}
};
UpgradeGift(controller, args.itemId, keepDetails, stars, done);
UpgradeGift(controller, args.itemId, keepDetails, cost, done);
});
auto star = session->data().customEmojiManager().creditsEmoji();
SetButtonMarkedLabel(
button,
tr::lng_gift_upgrade_button(
lt_price,
rpl::single(star.append(
' ' + Lang::FormatStarsAmountDecimal(StarsAmount{ 25 }))),
Ui::Text::WithEntities),
&controller->session(),
st::creditsBoxButtonLabel,
&st::giftBox.button.textFg);
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()
@ -2118,45 +2274,41 @@ void PaintPoints(
}
}
void ShowStarGiftUpgradeBox(
not_null<Window::SessionController*> controller,
uint64 stargiftId,
not_null<UserData*> user,
MsgId itemId,
int stars,
Fn<void(bool)> ready) {
const auto weak = base::make_weak(controller);
user->session().api().request(MTPpayments_GetStarGiftUpgradePreview(
MTP_long(stargiftId)
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
const auto weak = base::make_weak(args.controller);
const auto session = &args.user->session();
session->api().request(MTPpayments_GetStarGiftUpgradePreview(
MTP_long(args.stargiftId)
)).done([=](const MTPpayments_StarGiftUpgradePreview &result) {
const auto strong = weak.get();
if (!strong) {
ready(false);
if (const auto onstack = args.ready) {
onstack(false);
}
return;
}
const auto &data = result.data();
const auto session = &user->session();
auto args = UpgradeArgs{
.user = user,
.itemId = itemId,
.stars = stars,
};
auto upgrade = UpgradeArgs{ args };
for (const auto &attribute : data.vsample_attributes().v) {
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
args.models.push_back(Api::FromTL(session, data));
upgrade.models.push_back(Api::FromTL(session, data));
}, [&](const MTPDstarGiftAttributePattern &data) {
args.patterns.push_back(Api::FromTL(session, data));
upgrade.patterns.push_back(Api::FromTL(session, data));
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
args.backdrops.push_back(Api::FromTL(data));
upgrade.backdrops.push_back(Api::FromTL(data));
}, [](const auto &) {});
}
controller->show(Box(UpgradeBox, controller, std::move(args)));
ready(true);
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());
}
ready(false);
if (const auto onstack = args.ready) {
onstack(false);
}
}).send();
}

View file

@ -46,13 +46,17 @@ void PaintPoints(
const QRect &rect,
float64 shown = 1.);
void ShowStarGiftUpgradeBox(
not_null<Window::SessionController*> controller,
uint64 stargiftId,
not_null<UserData*> user,
MsgId itemId,
int stars,
Fn<void(bool)> ready);
struct StarGiftUpgradeArgs {
not_null<Window::SessionController*> controller;
base::required<uint64> stargiftId;
Fn<void(bool)> ready;
not_null<UserData*> user;
MsgId itemId = 0;
int cost = 0;
bool canAddSender = false;
bool canAddComment = false;
};
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args);
void AddUniqueCloseButton(not_null<GenericBox*> box);

View file

@ -64,6 +64,7 @@ struct CreditsHistoryEntry final {
uint64 barePeerId = 0;
uint64 bareGiveawayMsgId = 0;
uint64 bareGiftStickerId = 0;
uint64 bareGiftOwnerId = 0;
uint64 bareActorId = 0;
uint64 stargiftId = 0;
std::shared_ptr<UniqueGift> uniqueGift;
@ -87,6 +88,7 @@ struct CreditsHistoryEntry final {
bool fromGiftsList : 1 = false;
bool soldOutInfo : 1 = false;
bool canUpgradeGift : 1 = false;
bool hasGiftComment : 1 = false;
bool reaction : 1 = false;
bool refunded : 1 = false;
bool pending : 1 = false;

View file

@ -5407,7 +5407,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
const auto peer = isSelf ? _history->peer : _from;
const auto stars = action.vgift().match([&](
const MTPDstarGift &data) {
return uint64(data.vstars().v);
return uint64(data.vstars().v)
+ uint64(data.vupgrade_stars().value_or_empty());
}, [](const MTPDstarGiftUnique &) {
return uint64();
});

View file

@ -78,14 +78,24 @@ TextWithEntities PremiumGift::subtitle() {
return !_data.message.empty()
? _data.message
: outgoingGift()
? tr::lng_action_gift_sent_text(
? (_data.starsUpgradedBySender
? tr::lng_action_gift_sent_upgradable(
tr::now,
lt_user,
Ui::Text::Bold(_parent->history()->peer->shortName()),
Ui::Text::RichLangValue)
: tr::lng_action_gift_sent_text(
tr::now,
lt_count,
_data.starsConverted,
lt_user,
Ui::Text::Bold(_parent->history()->peer->shortName()),
Ui::Text::RichLangValue))
: _data.starsUpgradedBySender
? tr::lng_action_gift_got_upgradable_text(
tr::now,
lt_count,
_data.starsConverted,
lt_user,
Ui::Text::Bold(_parent->history()->peer->shortName()),
Ui::Text::RichLangValue)
: (_data.converted
: ((_data.converted || !_data.starsConverted)
? tr::lng_gift_got_stars
: tr::lng_action_gift_got_stars_text)(
tr::now,
@ -147,6 +157,8 @@ rpl::producer<QString> PremiumGift::button() {
? nullptr
: creditsPrize()
? tr::lng_view_button_giftcode()
: (starGift() && _data.starsUpgradedBySender && !_data.upgraded)
? tr::lng_gift_view_unpack()
: (gift() && (outgoingGift() || !_data.unclaimed))
? tr::lng_sticker_premium_view()
: tr::lng_prize_open();

View file

@ -96,9 +96,9 @@ public:
ButtonPart(
const QString &text,
QMargins margins,
QColor bg,
Fn<void()> repaint,
ClickHandlerPtr link);
ClickHandlerPtr link,
QColor bg = QColor(0, 0, 0, 0));
void draw(
Painter &p,
@ -126,6 +126,7 @@ private:
ClickHandlerPtr _link;
std::unique_ptr<Ui::RippleAnimation> _ripple;
mutable Ui::Premium::ColoredMiniStars _stars;
mutable std::optional<QColor> _starsLastColor;
Fn<void()> _repaint;
mutable QPoint _lastPoint;
@ -135,9 +136,9 @@ private:
ButtonPart::ButtonPart(
const QString &text,
QMargins margins,
QColor bg,
Fn<void()> repaint,
ClickHandlerPtr link)
ClickHandlerPtr link,
QColor bg)
: _text(st::semiboldTextStyle, text)
, _margins(margins)
, _bg(bg)
@ -152,13 +153,6 @@ ButtonPart::ButtonPart(
repaint();
}, Ui::Premium::MiniStars::Type::SlowStars)
, _repaint(std::move(repaint)) {
_stars.setColorOverride(QGradientStops{
{ 0., QColor(255, 255, 255, 255 * .3) },
{ 1., QColor(255, 255, 255) },
});
const auto padding = _size.height() / 2;
_stars.setCenter(
Rect(_size) - QMargins(padding, 0, padding, 0));
}
void ButtonPart::draw(
@ -168,17 +162,32 @@ void ButtonPart::draw(
int outerWidth) const {
PainterHighQualityEnabler hq(p);
const auto position = QPoint(
const auto customColors = (_bg.alpha() > 0);
const auto position = QPoint(
(outerWidth - width()) / 2 + _margins.left(),
_margins.top());
p.translate(position);
p.setPen(Qt::NoPen);
p.setBrush(_bg);
p.setBrush(customColors ? QBrush(_bg) : context.st->msgServiceBg());
const auto radius = _size.height() / 2.;
const auto r = Rect(_size);
p.drawRoundedRect(r, radius, radius);
auto white = QColor(255, 255, 255);
const auto fg = customColors ? white : context.st->msgServiceFg()->c;
if (!_starsLastColor || *_starsLastColor != fg) {
_starsLastColor = fg;
_stars.setColorOverride(QGradientStops{
{ 0., anim::with_alpha(fg, .3) },
{ 1., fg },
});
const auto padding = _size.height() / 2;
_stars.setCenter(
Rect(_size) - QMargins(padding, 0, padding, 0));
}
auto clipPath = QPainterPath();
clipPath.addRoundedRect(r, radius, radius);
p.setClipPath(clipPath);
@ -186,20 +195,22 @@ void ButtonPart::draw(
_stars.paint(p);
p.setClipping(false);
auto white = QColor(255, 255, 255);
p.setPen(white);
if (_ripple) {
const auto opacity = p.opacity();
const auto ripple = customColors
? anim::with_alpha(fg, .3)
: context.messageStyle()->msgWaveformInactive->c;
p.setOpacity(st::historyPollRippleOpacity);
white.setAlphaF(0.3);
_ripple->paint(
p,
0,
0,
width(),
&white);
&ripple);
p.setOpacity(opacity);
}
p.setPen(fg);
_text.draw(
p,
0,
@ -456,9 +467,9 @@ auto GenerateUniqueGiftMedia(
push(std::make_unique<ButtonPart>(
tr::lng_sticker_premium_view(tr::now),
st::chatUniqueButtonPadding,
gift->backdrop.patternColor,
[=] { parent->repaint(); },
std::move(link)));
std::move(link),
gift->backdrop.patternColor));
}
};
}
@ -537,4 +548,13 @@ Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
};
}
std::unique_ptr<MediaGenericPart> MakeGenericButtonPart(
const QString &text,
QMargins margins,
Fn<void()> repaint,
ClickHandlerPtr link,
QColor bg) {
return std::make_unique<ButtonPart>(text, margins, repaint, link, bg);
}
} // namespace HistoryView

View file

@ -29,8 +29,15 @@ auto GenerateUniqueGiftMedia(
not_null<Data::UniqueGift*> gift)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
[[nodiscard]] Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
not_null<Element*> view,
not_null<Data::UniqueGift*> gift);
[[nodiscard]] std::unique_ptr<MediaGenericPart> MakeGenericButtonPart(
const QString &text,
QMargins margins,
Fn<void()> repaint,
ClickHandlerPtr link,
QColor bg = QColor(0, 0, 0, 0));
} // namespace HistoryView

View file

@ -326,6 +326,7 @@ void InnerWidget::showGift(int index) {
_window->show(Box(
::Settings::UserStarGiftBox,
_window,
_user,
_entries[index].gift));
}

View file

@ -383,7 +383,8 @@ MTPInputInvoice Form::inputInvoice() const {
using Flag = MTPDinputInvoiceStarGift::Flag;
return MTP_inputInvoiceStarGift(
MTP_flags((gift->anonymous ? Flag::f_hide_name : Flag(0))
| (gift->message.empty() ? Flag(0) : Flag::f_message)),
| (gift->message.empty() ? Flag(0) : Flag::f_message)
| (gift->upgraded ? Flag::f_include_upgrade : Flag(0))),
gift->user->inputUser,
MTP_long(gift->giftId),
MTP_textWithEntities(

View file

@ -180,6 +180,7 @@ struct InvoiceStarGift {
not_null<UserData*> user;
int limitedCount = 0;
bool anonymous = false;
bool upgraded = false;
};
struct InvoiceId {

View file

@ -1182,25 +1182,28 @@ void ReceiptCreditsBox(
box,
object_ptr<Ui::FlatLabel>(
box,
((couldConvert || nonConvertible)
? (e.savedToProfile
? tr::lng_action_gift_can_remove_text
: tr::lng_action_gift_got_gift_text)(
Ui::Text::WithEntities)
: rpl::combine(
(canConvert
? tr::lng_action_gift_got_stars_text
: tr::lng_gift_got_stars)(
lt_count,
rpl::single(e.starsConverted * 1.),
Ui::Text::RichLangValue),
tr::lng_paid_about_link()
) | rpl::map([](
TextWithEntities text,
QString link) {
return text.append(' ').append(
Ui::Text::Link(link));
})),
(e.starsUpgradedBySender
? tr::lng_action_gift_got_upgradable_text(
Ui::Text::RichLangValue)
: ((couldConvert || nonConvertible)
? (e.savedToProfile
? tr::lng_action_gift_can_remove_text
: tr::lng_action_gift_got_gift_text)(
Ui::Text::WithEntities)
: rpl::combine(
(canConvert
? tr::lng_action_gift_got_stars_text
: tr::lng_gift_got_stars)(
lt_count,
rpl::single(e.starsConverted * 1.),
Ui::Text::RichLangValue),
tr::lng_paid_about_link()
) | rpl::map([](
TextWithEntities text,
QString link) {
return text.append(' ').append(
Ui::Text::Link(link));
}))),
st::creditsBoxAbout)))->entity();
about->setClickHandlerFilter([=](const auto &...) {
Core::App().iv().openWithIvPreferred(
@ -1297,6 +1300,32 @@ void ReceiptCreditsBox(
done);
};
const auto upgradeGuard = std::make_shared<bool>();
const auto upgrade = [=] {
if (const auto window = weakWindow.get()) {
const auto itemId = MsgId(e.bareMsgId);
if (*upgradeGuard) {
return;
}
*upgradeGuard = true;
using namespace Ui;
ShowStarGiftUpgradeBox({
.controller = window,
.stargiftId = e.stargiftId,
.ready = [=](bool) { *upgradeGuard = false; },
.user = starGiftSender,
.itemId = itemId,
.cost = e.starsUpgradedBySender ? 0 : e.starsToUpgrade,
.canAddSender = !e.anonymous,
.canAddComment = !e.anonymous && e.hasGiftComment,
});
}
};
const auto canUpgrade = e.stargiftId
&& e.canUpgradeGift
&& !e.uniqueGift;
const auto canUpgradeFree = canUpgrade && (e.starsUpgradedBySender > 0);
if (isStarGift && e.id.isEmpty()) {
const auto convert = [=, weak = Ui::MakeWeak(box)] {
const auto stars = e.starsConverted;
@ -1340,28 +1369,7 @@ void ReceiptCreditsBox(
}
});
};
const auto upgradeGuard = std::make_shared<bool>();
const auto upgrade = [=] {
if (const auto window = weakWindow.get()) {
const auto itemId = MsgId(e.bareMsgId);
if (*upgradeGuard) {
return;
}
*upgradeGuard = true;
using namespace Ui;
ShowStarGiftUpgradeBox(
window,
e.stargiftId,
starGiftSender,
itemId,
e.starsUpgradedBySender ? 0 : e.starsToUpgrade,
[=](bool) { *upgradeGuard = false; });
}
};
const auto canToggle = canConvert || couldConvert || nonConvertible;
const auto canUpgrade = e.stargiftId
&& e.canUpgradeGift
&& !e.uniqueGift;
AddStarGiftTable(
controller,
@ -1479,6 +1487,8 @@ void ReceiptCreditsBox(
? tr::lng_credits_subscription_off_button()
: toRejoin
? tr::lng_credits_subscription_off_rejoin_button()
: canUpgradeFree
? tr::lng_gift_upgrade_free()
: tr::lng_box_ok()));
const auto send = [=, weak = Ui::MakeWeak(box)] {
if (toRejoin) {
@ -1531,6 +1541,8 @@ void ReceiptCreditsBox(
if (willBusy) {
state->confirmButtonBusy = true;
send();
} else if (canUpgradeFree) {
upgrade();
} else {
box->closeBox();
}
@ -1607,6 +1619,7 @@ void CreditsPrizeBox(
void UserStarGiftBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<UserData*> owner,
const Data::UserStarGift &data) {
Settings::ReceiptCreditsBox(
box,
@ -1618,7 +1631,9 @@ void UserStarGiftBox(
.bareMsgId = uint64(data.messageId.bare),
.barePeerId = data.fromId.value,
.bareGiftStickerId = data.info.document->id,
.bareGiftOwnerId = owner->id.value,
.stargiftId = data.info.id,
.uniqueGift = data.info.unique,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
.limitedCount = data.info.limitedCount,
.limitedLeft = data.info.limitedLeft,
@ -1650,6 +1665,7 @@ void StarGiftViewBox(
.bareMsgId = uint64(item->id.bare),
.barePeerId = item->history()->peer->id.value,
.bareGiftStickerId = data.document ? data.document->id : 0,
.bareGiftOwnerId = item->history()->session().userPeerId().value,
.stargiftId = data.stargiftId,
.uniqueGift = data.unique,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
@ -1663,6 +1679,7 @@ void StarGiftViewBox(
.stargift = true,
.savedToProfile = data.saved,
.canUpgradeGift = data.upgradable,
.hasGiftComment = !data.message.empty(),
.in = true,
.gift = true,
};

View file

@ -100,6 +100,7 @@ void CreditsPrizeBox(
void UserStarGiftBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<UserData*> owner,
const Data::UserStarGift &data);
void StarGiftViewBox(
not_null<Ui::GenericBox*> box,

View file

@ -120,7 +120,8 @@ giftBoxButtonBottom: 12px;
giftBoxButtonPadding: margins(8px, 4px, 8px, 4px);
giftBoxPreviewStickerPadding: margins(10px, 12px, 10px, 16px);
giftBoxPreviewTitlePadding: margins(12px, 4px, 12px, 4px);
giftBoxPreviewTextPadding: margins(12px, 4px, 12px, 24px);
giftBoxPreviewTextPadding: margins(12px, 4px, 12px, 4px);
giftBoxButtonMargin: margins(12px, 8px, 12px, 12px);
giftBoxStickerTop: 0px;
giftBoxStickerStarTop: 24px;
giftBoxStickerSize: size(80px, 80px);
@ -192,3 +193,6 @@ uniqueCloseButton: IconButton(boxTitleClose) {
color: shadowFg;
}
}
upgradeGiftBox: Box(giftBox) {
buttonPadding: margins(22px, 3px, 22px, 22px);
}