Initial no-design craft implementation.

This commit is contained in:
John Preston
2026-01-16 16:32:56 +04:00
parent aa56a7362a
commit 1cb648c103
14 changed files with 694 additions and 95 deletions
+2
View File
@@ -337,6 +337,8 @@ PRIVATE
boxes/star_gift_auction_box.h
boxes/star_gift_box.cpp
boxes/star_gift_box.h
boxes/star_gift_craft_box.cpp
boxes/star_gift_craft_box.h
boxes/star_gift_preview_box.cpp
boxes/star_gift_preview_box.h
boxes/star_gift_resale_box.cpp
+28
View File
@@ -3816,6 +3816,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_collectible_tag" = "gift";
"lng_gift_crafted_tag" = "crafted";
"lng_gift_epic_tag" = "epic";
"lng_gift_legendary_tag" = "legendary";
"lng_gift_view_unpack" = "Unpack";
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
"lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name.";
@@ -4094,6 +4097,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_collection_reorder_exit" = "Apply Reorder";
"lng_gift_collection_remove_from" = "Remove from Collection";
"lng_gift_locked_title" = "Gift Locked";
"lng_gift_craft_info_level" = "Level {n} Gift";
"lng_gift_craft_info_title" = "Gift Crafting";
"lng_gift_craft_info_about" = "Use your existing gifts to craft new ones.";
"lng_gift_craft_combine_title" = "Combine Gifts";
"lng_gift_craft_combine_about" = "Add up to 4 NFT Gifts to attempt crafting a new upgraded model.";
"lng_gift_craft_input_title" = "Input Matters";
"lng_gift_craft_input_about" = "Each craft has a success chance. Better combinations improve the outcome.";
"lng_gift_craft_exclusive_title" = "Exclusive Look";
"lng_gift_craft_exclusive_about" = "Reforge gifts into a rarer collectibles with a new look.";
"lng_gift_craft_start_button" = "Start Crafting";
"lng_gift_craft_title" = "Craft Gift";
"lng_gift_craft_about1" = "Add up to **4** gifts to craft new {gift}.";
"lng_gift_craft_about2" = "If crafting fails, all selected gifts will be **consumed**.";
"lng_gift_craft_view_all" = "{emoji} View all new variants {arrow}";
"lng_gift_craft_button" = "Craft {gift}";
"lng_gift_craft_button_chance" = "{percent} Success Chance";
"lng_gift_craft_select_title" = "Select Gifts";
"lng_gift_craft_select_about" = "You need to add up to **3 gifts** from the same collection for crafting.";
"lng_gift_craft_select_your" = "Your gifts";
"lng_gift_craft_select_none" = "You don't have other gifts from this collection.";
"lng_gift_craft_select_market#one" = "{count} suitable gift on sale";
"lng_gift_craft_select_market#other" = "{count} suitable gifts on sale";
"lng_gift_craft_progress" = "Crafting";
"lng_gift_craft_progress_chance" = "{percent} success chance";
"lng_gift_craft_progress_fails" = "If crafting fails, all selected gifts will be consumed.";
"lng_auction_about_title" = "Auction";
"lng_auction_about_subtitle" = "Join the battle for exclusive gifts.";
+21 -3
View File
@@ -950,6 +950,7 @@ std::optional<Data::StarGift> FromTL(
.number = data.vnum().v,
.onlyAcceptTon = data.is_resale_ton_only(),
.canBeTheme = data.is_theme_available(),
.crafted = data.is_crafted(),
.model = *model,
.pattern = *pattern,
.value = (data.vvalue_amount()
@@ -999,6 +1000,9 @@ std::optional<Data::SavedStarGift> FromTL(
unique->exportAt = data.vcan_export_at().value_or_empty();
unique->canTransferAt = data.vcan_transfer_at().value_or_empty();
unique->canResellAt = data.vcan_resell_at().value_or_empty();
unique->canCraftAt = data.vcan_craft_at().value_or_empty();
unique->craftChancePermille
= data.vcraft_chance_permille().value_or_empty();
}
using Id = Data::SavedStarGiftId;
const auto hasUnique = parsed->unique != nullptr;
@@ -1045,7 +1049,17 @@ Data::UniqueGiftModel FromTL(
.document = session->data().processDocument(data.vdocument()),
};
result.name = qs(data.vname());
result.rarityPermille = data.vrarity_permille().v;
data.vrarity().match([&](const MTPDstarGiftAttributeRarity &data) {
result.rarityPermille = data.vpermille().v;
}, [](const auto &) {});
//MTPDstarGiftAttributeRarityRare &c_starGiftAttributeRarityRare() const;
//[[nodiscard]] const MTPDstarGiftAttributeRarityEpic &c_starGiftAttributeRarityEpic() const;
//[[nodiscard]] const MTPDstarGiftAttributeRarityLegendary &c_starGiftAttributeRarityLegendary() const;
//[[nodiscard]] const MTPDstarGiftAttributeRarityMythic &c_starGiftAttributeRarityMythic() const;
// );
//data.vrarity().c_starGiftAttributeRarity();
//data.vrarity().c_()
//result.rarityPermille = data.vrarity_permille().v;
return result;
}
@@ -1057,14 +1071,18 @@ Data::UniqueGiftPattern FromTL(
};
result.document->overrideEmojiUsesTextColor(true);
result.name = qs(data.vname());
result.rarityPermille = data.vrarity_permille().v;
data.vrarity().match([&](const MTPDstarGiftAttributeRarity &data) {
result.rarityPermille = data.vpermille().v;
}, [](const auto &) {});
return result;
}
Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) {
auto result = Data::UniqueGiftBackdrop{ .id = data.vbackdrop_id().v };
result.name = qs(data.vname());
result.rarityPermille = data.vrarity_permille().v;
data.vrarity().match([&](const MTPDstarGiftAttributeRarity &data) {
result.rarityPermille = data.vpermille().v;
}, [](const auto &) {});
result.centerColor = Ui::ColorFromSerialized(
data.vcenter_color());
result.edgeColor = Ui::ColorFromSerialized(
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_domain.h" // kMaxAccounts
#include "ui/chat/chat_theme.h"
#include "ui/chat/chat_style.h"
#include "ui/controls/feature_list.h"
#include "ui/layers/generic_box.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/effects/premium_graphics.h"
@@ -904,42 +905,28 @@ struct VideoPreviewDocument {
}
void AddGiftsInfoRows(not_null<Ui::VerticalLayout*> container) {
const auto infoRow = [&](
rpl::producer<QString> title,
rpl::producer<QString> text,
not_null<const style::icon*> icon) {
auto raw = container->add(
object_ptr<Ui::VerticalLayout>(container));
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(title) | rpl::map(tr::bold),
st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding);
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(text),
st::upgradeGiftSubtext),
st::settingsPremiumRowAboutPadding);
object_ptr<Info::Profile::FloatingIcon>(
raw,
*icon,
st::starrefInfoIconPosition);
const auto features = std::vector<Ui::FeatureListEntry>{
{
st::menuIconUnique,
tr::lng_gift_upgrade_unique_title(tr::now),
tr::lng_gift_upgrade_unique_about(tr::now, tr::marked),
},
{
st::menuIconTradable,
tr::lng_gift_upgrade_tradable_title(tr::now),
tr::lng_gift_upgrade_tradable_about(tr::now, tr::marked),
},
{
st::menuIconNftWear,
tr::lng_gift_upgrade_wearable_title(tr::now),
tr::lng_gift_upgrade_wearable_about(tr::now, tr::marked),
},
};
infoRow(
tr::lng_gift_upgrade_unique_title(),
tr::lng_gift_upgrade_unique_about(),
&st::menuIconUnique);
infoRow(
tr::lng_gift_upgrade_tradable_title(),
tr::lng_gift_upgrade_tradable_about(),
&st::menuIconTradable);
infoRow(
tr::lng_gift_upgrade_wearable_title(),
tr::lng_gift_upgrade_wearable_about(),
&st::menuIconNftWear);
for (const auto &feature : features) {
container->add(
Ui::MakeFeatureListEntry(container, feature),
st::boxRowPadding);
}
}
void PreviewBox(
+48 -54
View File
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_global_privacy.h"
#include "api/api_premium.h"
#include "api/api_text_entities.h"
//#include "base/call_delayed.h"
#include "base/event_filter.h"
#include "base/qt_signal_producer.h"
#include "base/random.h"
@@ -85,6 +84,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/chat_theme.h"
#include "ui/controls/button_labels.h"
#include "ui/controls/emoji_button.h"
#include "ui/controls/feature_list.h"
#include "ui/controls/ton_common.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/path_shift_gradient.h"
@@ -5224,60 +5224,54 @@ void UpgradeBox(
AddSkip(container, st::defaultVerticalListSkip * 2);
const auto infoRow = [&](
rpl::producer<QString> title,
rpl::producer<QString> text,
not_null<const style::icon*> icon) {
auto raw = container->add(
object_ptr<Ui::VerticalLayout>(container));
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(title) | rpl::map(tr::bold),
st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding);
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(text),
st::upgradeGiftSubtext),
st::settingsPremiumRowAboutPadding);
object_ptr<Info::Profile::FloatingIcon>(
raw,
*icon,
st::starrefInfoIconPosition);
const auto features = std::vector<Ui::FeatureListEntry>{
{
st::menuIconUnique,
tr::lng_gift_upgrade_unique_title(tr::now),
(args.savedId
? tr::lng_gift_upgrade_unique_about(tr::now, tr::marked)
: (args.peer->isBroadcast()
? tr::lng_gift_upgrade_unique_about_channel
: tr::lng_gift_upgrade_unique_about_user)(
tr::now,
lt_name,
tr::marked(args.peer->shortName()),
tr::marked)),
},
{
st::menuIconReplace,
tr::lng_gift_upgrade_transferable_title(tr::now),
(args.savedId
? tr::lng_gift_upgrade_transferable_about(
tr::now,
tr::marked)
: (args.peer->isBroadcast()
? tr::lng_gift_upgrade_transferable_about_channel
: tr::lng_gift_upgrade_transferable_about_user)(
tr::now,
lt_name,
tr::marked(args.peer->shortName()),
tr::marked)),
},
{
st::menuIconTradable,
tr::lng_gift_upgrade_tradable_title(tr::now),
(args.savedId
? tr::lng_gift_upgrade_tradable_about(tr::now, tr::marked)
: (args.peer->isBroadcast()
? tr::lng_gift_upgrade_tradable_about_channel
: tr::lng_gift_upgrade_tradable_about_user)(
tr::now,
lt_name,
tr::marked(args.peer->shortName()),
tr::marked)),
},
};
infoRow(
tr::lng_gift_upgrade_unique_title(),
(args.savedId
? tr::lng_gift_upgrade_unique_about()
: (args.peer->isBroadcast()
? tr::lng_gift_upgrade_unique_about_channel
: tr::lng_gift_upgrade_unique_about_user)(
lt_name,
rpl::single(args.peer->shortName()))),
&st::menuIconUnique);
infoRow(
tr::lng_gift_upgrade_transferable_title(),
(args.savedId
? tr::lng_gift_upgrade_transferable_about()
: (args.peer->isBroadcast()
? tr::lng_gift_upgrade_transferable_about_channel
: tr::lng_gift_upgrade_transferable_about_user)(
lt_name,
rpl::single(args.peer->shortName()))),
&st::menuIconReplace);
infoRow(
tr::lng_gift_upgrade_tradable_title(),
(args.savedId
? tr::lng_gift_upgrade_tradable_about()
: (args.peer->isBroadcast()
? tr::lng_gift_upgrade_tradable_about_channel
: tr::lng_gift_upgrade_tradable_about_user)(
lt_name,
rpl::single(args.peer->shortName()))),
&st::menuIconTradable);
for (const auto &feature : features) {
container->add(
Ui::MakeFeatureListEntry(container, feature),
st::boxRowPadding);
}
const auto gifting = !args.savedId
&& !args.giftPrepayUpgradeHash.isEmpty();
@@ -0,0 +1,497 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/star_gift_craft_box.h"
#include "apiwrap.h"
#include "api/api_credits.h"
#include "api/api_premium.h"
#include "boxes/star_gift_box.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_star_gift.h"
#include "data/data_user.h"
#include "info/peer_gifts/info_peer_gifts_common.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/boxes/about_cocoon_box.h" // AddUniqueCloseButton.
#include "ui/controls/button_labels.h"
#include "ui/controls/feature_list.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/buttons.h"
#include "ui/painter.h"
#include "ui/top_background_gradient.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_credits.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
namespace Ui {
namespace {
using namespace Info::PeerGifts;
[[nodiscard]] QImage CreateBgGradient(
QSize size,
const Data::UniqueGiftBackdrop &backdrop) {
const auto ratio = style::DevicePixelRatio();
auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
auto p = QPainter(&result);
auto hq = PainterHighQualityEnabler(p);
auto gradient = QRadialGradient(
QPoint(size.width() / 2, size.height() / 2),
size.height() / 2);
gradient.setStops({
{ 0., backdrop.centerColor },
{ 1., backdrop.edgeColor },
});
p.setBrush(gradient);
p.setPen(Qt::NoPen);
p.drawRect(0, 0, size.width(), size.height());
p.end();
const auto mask = Images::CornersMask(st::boxRadius);
return Images::Round(std::move(result), mask);
}
void ShowSelectGiftBox(
not_null<Window::SessionController*> controller,
std::vector<Data::SavedStarGift> list,
Fn<void(std::optional<Data::SavedStarGift>)> chosen,
bool exists) {
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(rpl::single(u"Choose Gift"_q));
box->setWidth(st::boxWideWidth);
struct Entry {
Data::SavedStarGift gift;
GiftButton *button = nullptr;
};
struct State {
explicit State(not_null<Main::Session*> session)
: delegate(session, GiftButtonMode::Minimal) {
}
Delegate delegate;
std::vector<Entry> entries;
rpl::event_stream<> changes;
rpl::variable<int> successPercentPermille;
rpl::variable<QString> successPercentText;
int requestingIndex = 0;
bool crafting = false;
};
const auto session = &controller->session();
const auto state = box->lifetime().make_state<State>(session);
const auto single = state->delegate.buttonSize();
const auto extend = state->delegate.buttonExtend();
const auto full = single.grownBy(extend).height();
const auto skip = st::boxRowPadding.left() / 2;
auto row = (RpWidget*)nullptr;
for (auto &gift : list) {
if (!row) {
row = box->addRow(object_ptr<RpWidget>(box), QMargins());
row->resize(row->width(), full);
}
const auto col = (state->entries.size() % 3);
state->entries.push_back(Entry{
.gift = gift,
.button = CreateChild<GiftButton>(
row,
&state->delegate),
});
const auto button = state->entries.back().button;
button->setClickedCallback([=] {
chosen(gift);
box->closeBox();
});
button->show();
button->setDescriptor(GiftTypeStars{
.info = {
.id = gift.info.id,
.unique = gift.info.unique,
.document = gift.info.unique->model.document,
},
}, GiftButton::Mode::Minimal);
const auto width = (st::boxWideWidth - 2 * skip - st::boxRowPadding.left() - st::boxRowPadding.right()) / 3;
const auto left = st::boxRowPadding.left() + (width + skip) * col;
button->setGeometry(QRect(left, extend.top(), width, single.height()), extend);
if (col == 2) {
row = nullptr;
}
}
box->addButton(tr::lng_box_ok(), [=] {
box->closeBox();
});
if (exists) {
box->addLeftButton(rpl::single(u"Remove"_q), [=] {
chosen(std::nullopt);
box->closeBox();
});
}
box->setMaxHeight(st::boxWideWidth);
}));
}
[[nodiscard]] object_ptr<RpWidget> MakeCraftContent(
not_null<GenericBox*> box,
not_null<Window::SessionController*> controller,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId) {
auto result = object_ptr<RpWidget>(box->verticalLayout());
const auto raw = result.data();
const auto width = st::boxWidth;
const auto height = st::boxWideWidth;
struct BackdropView {
Data::UniqueGiftBackdrop colors;
QImage gradient;
};
struct PatternView {
DocumentData *document = nullptr;
std::unique_ptr<Text::CustomEmoji> emoji;
base::flat_map<int, base::flat_map<float64, QImage>> emojis;
};
struct Entry {
Data::SavedStarGiftId id;
std::shared_ptr<Data::UniqueGift> gift;
GiftButton *button = nullptr;
AbstractButton *add = nullptr;
};
struct State {
explicit State(not_null<Main::Session*> session)
: delegate(session, GiftButtonMode::Minimal) {
}
Delegate delegate;
BackdropView backdrop;
PatternView pattern;
std::vector<Entry> entries;
rpl::event_stream<> changes;
rpl::variable<int> successPercentPermille;
rpl::variable<QString> successPercentText;
int requestingIndex = 0;
bool crafting = false;
};
const auto session = &controller->session();
const auto state = raw->lifetime().make_state<State>(session);
state->backdrop.colors.centerColor = QColor(42, 61, 82, 255);
state->backdrop.colors.edgeColor = QColor(35, 45, 63, 255);
state->entries.push_back(Entry{ savedId, gift });
for (auto i = 0; i != 3; ++i) {
state->entries.emplace_back();
}
state->successPercentPermille = state->changes.events_starting_with_copy(
rpl::empty
) | rpl::map([=] {
auto result = 0;
for (const auto &entry : state->entries) {
if (const auto gift = entry.gift.get()) {
result += gift->craftChancePermille;
}
}
return result;
});
state->successPercentText = state->successPercentPermille.value(
) | rpl::map([](int permille) {
return QString::number(permille / 10.) + '%';
});
const auto editEntry = [=](int index) {
const auto guard = base::make_weak(raw);
if (state->requestingIndex) {
state->requestingIndex = index;
return;
}
state->requestingIndex = index;
session->api().request(MTPpayments_GetCraftStarGifts(
MTP_long(gift->initialGiftId),
MTP_string(),
MTP_int(30)
)).done([=](const MTPpayments_SavedStarGifts &result) {
if (!guard) {
return;
}
const auto user = session->user();
const auto owner = &session->data();
const auto &data = result.data();
owner->processUsers(data.vusers());
owner->processChats(data.vchats());
auto list = std::vector<Data::SavedStarGift>();
list.reserve(data.vgifts().v.size());
for (const auto &gift : data.vgifts().v) {
if (auto parsed = Api::FromTL(user, gift)) {
list.push_back(std::move(*parsed));
}
}
const auto index = base::take(state->requestingIndex);
ShowSelectGiftBox(controller, list, [=](std::optional<Data::SavedStarGift> chosen) {
if (chosen) {
state->entries[index].gift = chosen->info.unique;
state->entries[index].id = chosen->manageId;
} else {
state->entries[index].gift = nullptr;
state->entries[index].id = Data::SavedStarGiftId();
}
state->changes.fire({});
}, (state->entries[index].gift != nullptr));
}).send();
};
const auto refreshButton = [=](int index) {
Expects(index >= 0 && index < state->entries.size());
auto &entry = state->entries[index];
const auto single = state->delegate.buttonSize();
const auto skip = st::boxTitleClose.width;
const auto geometry = QRect(
(index % 2) ? (width - skip - single.width()) : skip,
(index < 2) ? skip : (height - st::giftBox.buttonPadding.bottom() - st::giftBox.buttonHeight - st::giftBox.buttonPadding.top() - single.height()),
single.width(),
single.height());
delete base::take(entry.add);
delete base::take(entry.button);
if (entry.gift) {
entry.button = CreateChild<GiftButton>(raw, &state->delegate);
entry.button->setDescriptor(GiftTypeStars{
.info = {
.id = entry.gift->initialGiftId,
.unique = entry.gift,
.document = entry.gift->model.document,
},
}, GiftButton::Mode::Minimal);
entry.button->show();
if (index > 0) {
entry.button->setClickedCallback([=] {
editEntry(index);
});
} else {
entry.button->setAttribute(Qt::WA_TransparentForMouseEvents);
}
entry.button->setGeometry(
geometry,
state->delegate.buttonExtend());
} else {
entry.add = CreateChild<AbstractButton>(raw);
entry.add->show();
entry.add->paintOn([=](QPainter &p) {
auto hq = PainterHighQualityEnabler(p);
const auto radius = st::boxRadius;
p.setPen(Qt::NoPen);
p.setBrush(QColor(57, 77, 99));
p.drawRoundedRect(
QRect(QPoint(), geometry.size()),
radius,
radius);
const auto addSize = geometry.width() / 3;
p.setBrush(QColor(255, 255, 255));
p.drawEllipse(addSize, (geometry.height() - addSize) / 2, addSize, addSize);
const auto plusSize = geometry.width() / 6;
p.setPen(QPen(QBrush(QColor(57, 77, 99)), plusSize / 5));
p.setBrush(Qt::NoBrush);
p.drawLine((geometry.width() - plusSize) / 2, geometry.height() / 2, (geometry.width() - plusSize) / 2 + plusSize, geometry.height() / 2);
p.drawLine(geometry.width() / 2, (geometry.height() - plusSize) / 2, geometry.width() / 2, (geometry.height() - plusSize) / 2 + plusSize);
});
entry.add->setClickedCallback([=] {
editEntry(index);
});
entry.add->setGeometry(geometry);
}
};
state->changes.events_starting_with_copy(rpl::empty) | rpl::on_next([=] {
for (auto i = 0; i != 4; ++i) {
refreshButton(i);
}
}, raw->lifetime());
const auto setupPattern = [=](
PatternView &to,
const Data::UniqueGiftPattern &pattern) {
const auto document = pattern.document;
const auto callback = [=] {
if (state->pattern.document == document) {
raw->update();
}
};
to.document = document;
to.emoji = document->owner().customEmojiManager().create(
document,
callback,
Data::CustomEmojiSizeTag::Large);
[[maybe_unused]] const auto preload = to.emoji->ready();
};
setupPattern(state->pattern, gift->pattern);
raw->paintOn([=](QPainter &p) {
const auto width = raw->width();
const auto getBackdrop = [&](BackdropView &backdrop) {
const auto ratio = style::DevicePixelRatio();
const auto gradientSize = QSize(width, raw->height());
auto &gradient = backdrop.gradient;
if (gradient.size() != gradientSize * ratio) {
gradient = CreateBgGradient(gradientSize, backdrop.colors);
}
return gradient;
};
const auto paintPattern = [&](
QPainter &p,
PatternView &pattern,
const BackdropView &backdrop,
float64 shown) {
const auto color = backdrop.colors.patternColor;
const auto key = (color.red() << 16)
| (color.green() << 8)
| color.blue();
PaintBgPoints(
p,
PatternBgPoints(),
pattern.emojis[key],
pattern.emoji.get(),
color,
QRect(0, 0, width, st::uniqueGiftSubtitleTop),
shown);
};
p.drawImage(0, 0, getBackdrop(state->backdrop));
paintPattern(p, state->pattern, state->backdrop, 1.);
});
raw->resize(width, height);
const auto button = CreateChild<RoundButton>(
raw,
rpl::single(QString()),
st::giftBox.button);
button->setClickedCallback([=] {
if (state->crafting) {
return;
}
state->crafting = true;
auto inputs = QVector<MTPInputSavedStarGift>();
for (const auto &entry : state->entries) {
if (entry.gift) {
inputs.push_back(
Api::InputSavedStarGiftId(entry.id, entry.gift));
}
}
const auto weak = base::make_weak(controller);
session->api().request(MTPpayments_CraftStarGift(
MTP_vector<MTPInputSavedStarGift>(inputs)
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
if (const auto strong = weak.get()) {
strong->showPeerHistory(strong->session().user()->id);
}
}).send();
});
raw->sizeValue() | rpl::on_next([=](QSize size) {
const auto width = size.width();
const auto height = size.height();
const auto padding = st::giftBox.buttonPadding;
button->setFullWidth(width - padding.left() - padding.right());
button->moveToLeft(
padding.left(),
height - padding.bottom() - button->height());
}, raw->lifetime());
SetButtonTwoLabels(
button,
tr::lng_gift_craft_button(
lt_gift,
rpl::single(tr::marked(Data::UniqueGiftName(*gift))),
tr::marked),
tr::lng_gift_craft_button_chance(
lt_percent,
state->successPercentText.value() | rpl::map(tr::marked),
tr::marked),
st::resaleButtonTitle,
st::resaleButtonSubtitle);
return result;
}
void ShowGiftCraftBox(
not_null<Window::SessionController*> controller,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId) {
controller->show(Box([=](not_null<GenericBox*> box) {
box->setStyle(st::giftCraftBox);
box->setWidth(st::boxWidth);
box->setNoContentMargin(true);
box->addRow(
MakeCraftContent(box, controller, gift, savedId), QMargins());
AddUniqueCloseButton(box);
}));
}
} // namespace
void ShowGiftCraftInfoBox(
not_null<Window::SessionController*> controller,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId) {
controller->show(Box([=](not_null<GenericBox*> box) {
const auto container = box->verticalLayout();
auto cover = tr::lng_gift_craft_info_title(
) | rpl::map([=](const QString &title) {
auto result = UniqueGiftCover{ *gift };
result.values.title = title;
return result;
});
AddUniqueGiftCover(container, std::move(cover), {
.pretitle = tr::lng_gift_craft_info_level(
lt_n,
rpl::single(QString::number(2))),
.subtitle = tr::lng_gift_craft_info_about(tr::marked),
});
AddSkip(container);
AddUniqueCloseButton(box);
const auto features = std::vector<FeatureListEntry>{
{
st::menuIconUnique,
tr::lng_gift_craft_combine_title(tr::now),
tr::lng_gift_craft_combine_about(tr::now, tr::rich),
},
{
st::menuIconTradable,
tr::lng_gift_craft_input_title(tr::now),
tr::lng_gift_craft_input_about(tr::now, tr::marked),
},
{
st::menuIconNftWear,
tr::lng_gift_craft_exclusive_title(tr::now),
tr::lng_gift_craft_exclusive_about(tr::now, tr::marked),
},
};
for (const auto &feature : features) {
container->add(
MakeFeatureListEntry(container, feature),
st::boxRowPadding);
}
box->setStyle(st::giftBox);
box->setWidth(st::boxWideWidth);
box->setNoContentMargin(true);
box->addButton(tr::lng_gift_craft_start_button(), [=] {
ShowGiftCraftBox(controller, gift, savedId);
});
}));
}
} // namespace Ui
@@ -0,0 +1,26 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Data {
struct UniqueGift;
class SavedStarGiftId;
} // namespace Data
namespace Window {
class SessionController;
} // namespace Window
namespace Ui {
void ShowGiftCraftInfoBox(
not_null<Window::SessionController*> controller,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId);
} // namespace Ui
@@ -203,6 +203,7 @@ struct GiftCode {
bool refunded : 1 = false;
bool upgrade : 1 = false;
bool saved : 1 = false;
bool craft : 1 = false;
};
class Media {
@@ -97,15 +97,18 @@ struct UniqueGift {
PeerData *releasedBy = nullptr;
PeerData *themeUser = nullptr;
int64 nanoTonForResale = -1;
int craftChancePermille = 0;
int starsForResale = -1;
int starsForTransfer = -1;
int starsMinOffer = -1;
int number = 0;
bool onlyAcceptTon = false;
bool canBeTheme = false;
bool crafted = false;
TimeId exportAt = 0;
TimeId canTransferAt = 0;
TimeId canResellAt = 0;
TimeId canCraftAt = 0;
UniqueGiftModel model;
UniqueGiftPattern pattern;
UniqueGiftBackdrop backdrop;
@@ -6925,6 +6925,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
.refunded = data.is_refunded(),
.upgrade = data.is_upgrade(),
.saved = data.is_saved(),
.craft = data.is_craft(),
};
if (auto gift = Api::FromTL(&history()->session(), data.vgift())) {
fields.stargiftId = gift->id;
@@ -6940,6 +6941,9 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
unique->exportAt = data.vcan_export_at().value_or_empty();
unique->canTransferAt = data.vcan_transfer_at().value_or_empty();
unique->canResellAt = data.vcan_resell_at().value_or_empty();
unique->canCraftAt = data.vcan_craft_at().value_or_empty();
unique->craftChancePermille
= data.vcraft_chance_permille().value_or_empty();
}
}
_media = std::make_unique<Data::MediaGiftBox>(
+9 -3
View File
@@ -1955,9 +1955,9 @@ botVerifierSettings#b0cd6617 flags:# can_modify_custom_description:flags.1?true
botVerification#f93cd45c bot_id:long icon:long description:string = BotVerification;
starGiftAttributeModel#39d99013 name:string document:Document rarity_permille:int = StarGiftAttribute;
starGiftAttributePattern#13acff19 name:string document:Document rarity_permille:int = StarGiftAttribute;
starGiftAttributeBackdrop#d93d859c name:string backdrop_id:int center_color:int edge_color:int pattern_color:int text_color:int rarity_permille:int = StarGiftAttribute;
starGiftAttributeModel#565251e2 flags:# crafted:flags.0?true name:string document:Document rarity:StarGiftAttributeRarity = StarGiftAttribute;
starGiftAttributePattern#4e7085ea name:string document:Document rarity:StarGiftAttributeRarity = StarGiftAttribute;
starGiftAttributeBackdrop#9f2504e4 name:string backdrop_id:int center_color:int edge_color:int pattern_color:int text_color:int rarity:StarGiftAttributeRarity = StarGiftAttribute;
starGiftAttributeOriginalDetails#e0bff26c flags:# sender_id:flags.0?Peer recipient_id:Peer date:int message:flags.1?TextWithEntities = StarGiftAttribute;
payments.starGiftUpgradePreview#3de1dfed sample_attributes:Vector<StarGiftAttribute> prices:Vector<StarGiftUpgradePrice> next_prices:Vector<StarGiftUpgradePrice> = payments.StarGiftUpgradePreview;
@@ -2114,6 +2114,12 @@ messages.emojiGameOutcome#da2ad647 seed:bytes stake_ton_amount:long ton_amount:l
messages.emojiGameUnavailable#59e65335 = messages.EmojiGameInfo;
messages.emojiGameDiceInfo#44e56023 flags:# game_hash:string prev_stake:long current_streak:int params:Vector<int> plays_left:flags.0?int = messages.EmojiGameInfo;
starGiftAttributeRarity#36437737 permille:int = StarGiftAttributeRarity;
starGiftAttributeRarityRare#f08d516b = StarGiftAttributeRarity;
starGiftAttributeRarityEpic#78fbf3a8 = StarGiftAttributeRarity;
starGiftAttributeRarityLegendary#cef7e7a8 = StarGiftAttributeRarity;
starGiftAttributeRarityMythic#f8374495 = StarGiftAttributeRarity;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/gift_premium_box.h"
#include "boxes/share_box.h"
#include "boxes/star_gift_box.h"
#include "boxes/star_gift_craft_box.h"
#include "boxes/star_gift_resale_box.h"
#include "boxes/transfer_gift_box.h"
#include "chat_helpers/stickers_gift_box_pack.h"
@@ -963,6 +964,25 @@ void ProcessReceivedSubscriptions(
// (owner->isChannel() && owner->asChannel()->canTransferGifts());
}
[[nodiscard]] bool CanCraftGift(
not_null<Main::Session*> session,
const Data::CreditsHistoryEntry &e) {
const auto unique = e.uniqueGift.get();
if (!unique || !unique->craftChancePermille) {
return false;
}
const auto owner = (unique && unique->ownerId)
? session->data().peer(unique->ownerId).get()
: nullptr;
return !owner
? false
: owner->isSelf()
? e.in
: false;
// Currently we're not crafting channel gifts.
// (owner->isChannel() && owner->asChannel()->canTransferGifts());
}
[[nodiscard]] bool ShowOfferBuyButton(
not_null<Main::Session*> session,
const Data::CreditsHistoryEntry &e) {
@@ -1121,6 +1141,14 @@ void FillUniqueGiftMenu(
}
}, st.transfer ? st.transfer : &st::menuIconReplace);
}
if (CanCraftGift(&show->session(), e)) {
AssertIsDebug(icon and if we even put this here);
menu->addAction(tr::lng_gift_craft_title(tr::now), [=] {
if (const auto window = show->resolveWindow()) {
Ui::ShowGiftCraftInfoBox(window, unique, savedId);
}
}, st.transfer ? st.transfer : &st::menuIconReplace);
}
const auto wear = host->isSelf()
? e.in
: (host->isChannel() && host->asChannel()->canEditEmoji());
@@ -122,6 +122,7 @@ struct CreditsEntryBoxStyleOverrides {
const style::icon *share = nullptr;
const style::icon *theme = nullptr;
const style::icon *transfer = nullptr;
const style::icon *craft = nullptr;
const style::icon *wear = nullptr;
const style::icon *takeoff = nullptr;
const style::icon *resell = nullptr;
@@ -139,6 +139,10 @@ giftBox: Box(defaultBox) {
giftLimitedBox: Box(giftBox) {
buttonPadding: margins(22px, 48px, 22px, 12px);
}
giftCraftBox: Box(giftBox) {
buttonPadding: margins(0px, 0px, 0px, 0px);
buttonHeight: 0px;
}
giftLimitedPadding: margins(8px, 4px, 8px, 4px);
giftBoxSubtitle: FlatLabel(defaultFlatLabel) {
style: TextStyle(defaultTextStyle) {