Implement unique gift transfer.

This commit is contained in:
John Preston 2024-12-30 13:56:37 +04:00
parent 7491337bfd
commit 083400d1c2
19 changed files with 622 additions and 65 deletions

View file

@ -332,6 +332,8 @@ PRIVATE
boxes/sticker_set_box.h
boxes/stickers_box.cpp
boxes/stickers_box.h
boxes/transfer_gift_box.cpp
boxes/transfer_gift_box.h
boxes/translate_box.cpp
boxes/translate_box.h
boxes/url_auth_box.cpp

View file

@ -2025,6 +2025,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_unique_sent" = "You sent a unique collectible item";
"lng_action_gift_upgraded" = "{user} turned the gift from you to a unique collectible";
"lng_action_gift_upgraded_mine" = "You turned the gift from {user} to a unique collectible";
"lng_action_gift_transferred" = "{user} transferred you a gift";
"lng_action_gift_transferred_mine" = "You transferred a gift to {user}";
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
"lng_action_gift_for_stars#one" = "{count} Star";
"lng_action_gift_for_stars#other" = "{count} Stars";
@ -3239,6 +3241,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
"lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_collectible_tag" = "gift";
"lng_gift_price_unique" = "Unique";
"lng_gift_view_unpack" = "Unpack";
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";

View file

@ -806,8 +806,8 @@ std::optional<Data::StarGift> FromTL(
.id = uint64(data.vid().v),
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
.title = qs(data.vtitle()),
.number = data.vnum().v,
.ownerId = peerFromUser(UserId(data.vowner_id().v)),
.number = data.vnum().v,
.model = *model,
.pattern = *pattern,
}),
@ -837,6 +837,9 @@ std::optional<Data::UserStarGift> FromTL(
auto parsed = FromTL(session, data.vgift());
if (!parsed) {
return {};
} else if (const auto unique = parsed->unique.get()) {
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
unique->exportAt = data.vcan_export_at().value_or_empty();
}
return Data::UserStarGift{
.info = std::move(*parsed),

View file

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
#include "boxes/transfer_gift_box.h" // ShowTransferGiftBox.
#include "data/data_boosts.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
@ -1204,8 +1205,16 @@ void AddStarGiftTable(
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;
const auto transfer = entry.in
&& entry.bareMsgId
&& (unique->starsForTransfer >= 0);
auto send = transfer ? tr::lng_gift_unique_owner_change() : nullptr;
auto handler = transfer ? Fn<void()>([=] {
ShowTransferGiftBox(
controller->parentController(),
entry.uniqueGift,
MsgId(entry.bareMsgId));
}) : nullptr;
AddTableRow(
table,
tr::lng_gift_unique_owner(),

View file

@ -1129,11 +1129,10 @@ void ShowGiftUpgradedToast(
}
}
void SendUpgradeRequest(
void SendStarsFormRequest(
not_null<Window::SessionController*> controller,
Settings::SmallBalanceResult result,
uint64 formId,
int stars,
MTPInputInvoice invoice,
Fn<void(Payments::CheckoutResult)> done) {
using BalanceResult = Settings::SmallBalanceResult;
@ -1190,46 +1189,12 @@ void UpgradeGift(
return;
}
using Flag = MTPDinputInvoiceStarGiftUpgrade::Flag;
const auto weak = base::make_weak(window);
const auto invoice = MTP_inputInvoiceStarGiftUpgrade(
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
MTP_int(messageId.bare));
session->api().request(MTPpayments_GetPaymentForm(
MTP_flags(0),
invoice,
MTPDataJSON() // theme_params
)).done([=](const MTPpayments_PaymentForm &result) {
result.match([&](const MTPDpayments_paymentFormStarGift &data) {
const auto formId = data.vform_id().v;
const auto prices = data.vinvoice().data().vprices().v;
const auto strong = weak.get();
if (!strong) {
done(Payments::CheckoutResult::Failed);
return;
}
const auto ready = [=](Settings::SmallBalanceResult result) {
SendUpgradeRequest(
strong,
result,
formId,
stars,
invoice,
done);
};
Settings::MaybeRequestBalanceIncrease(
Main::MakeSessionShow(strong->uiShow(), session),
prices.front().data().vamount().v,
Settings::SmallBalanceDeepLink{},
ready);
}, [&](const auto &) {
done(Payments::CheckoutResult::Failed);
});
}).fail([=](const MTP::Error &error) {
if (const auto strong = weak.get()) {
strong->showToast(error.type());
}
done(Payments::CheckoutResult::Failed);
}).send();
RequestStarsFormAndSubmit(
window,
MTP_inputInvoiceStarGiftUpgrade(
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
MTP_int(messageId.bare)),
std::move(done));
}
void SoldOutBox(
@ -2327,4 +2292,41 @@ void AddUniqueCloseButton(not_null<GenericBox*> box) {
});
}
void RequestStarsFormAndSubmit(
not_null<Window::SessionController*> window,
MTPInputInvoice invoice,
Fn<void(Payments::CheckoutResult)> done) {
const auto weak = base::make_weak(window);
window->session().api().request(MTPpayments_GetPaymentForm(
MTP_flags(0),
invoice,
MTPDataJSON() // theme_params
)).done([=](const MTPpayments_PaymentForm &result) {
result.match([&](const MTPDpayments_paymentFormStarGift &data) {
const auto formId = data.vform_id().v;
const auto prices = data.vinvoice().data().vprices().v;
const auto strong = weak.get();
if (!strong) {
done(Payments::CheckoutResult::Failed);
return;
}
const auto ready = [=](Settings::SmallBalanceResult result) {
SendStarsFormRequest(strong, result, formId, invoice, done);
};
Settings::MaybeRequestBalanceIncrease(
strong->uiShow(),
prices.front().data().vamount().v,
Settings::SmallBalanceDeepLink{},
ready);
}, [&](const auto &) {
done(Payments::CheckoutResult::Failed);
});
}).fail([=](const MTP::Error &error) {
if (const auto strong = weak.get()) {
strong->showToast(error.type());
}
done(Payments::CheckoutResult::Failed);
}).send();
}
} // namespace Ui

View file

@ -13,6 +13,10 @@ struct GiftCode;
struct CreditsHistoryEntry;
} // namespace Data
namespace Payments {
enum class CheckoutResult;
} // namespace Payments
namespace Window {
class SessionController;
} // namespace Window
@ -60,4 +64,9 @@ void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args);
void AddUniqueCloseButton(not_null<GenericBox*> box);
void RequestStarsFormAndSubmit(
not_null<Window::SessionController*> window,
MTPInputInvoice invoice,
Fn<void(Payments::CheckoutResult)> done);
} // namespace Ui

View file

@ -0,0 +1,407 @@
/*
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/transfer_gift_box.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/data_star_gift.h"
#include "data/data_user.h"
#include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle.
#include "boxes/peer_list_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/star_gift_box.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "payments/payments_checkout_process.h"
#include "ui/boxes/confirm_box.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h" // peerListSingleRow.
#include "styles/style_dialogs.h" // recentPeersSpecialName.
namespace {
struct ExportOption {
object_ptr<Ui::RpWidget> content = { nullptr };
Fn<bool(int, int, int)> overrideKey;
Fn<void()> activate;
};
class Controller final : public ContactsBoxController {
public:
Controller(
not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift,
Fn<void(not_null<PeerData*>)> choose);
void noSearchSubmit();
private:
void prepareViewHook() override;
void setupExportOption();
bool overrideKeyboardNavigation(
int direction,
int fromIndex,
int toIndex) override;
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override;
void rowClicked(not_null<PeerListRow*> row) override;
const std::shared_ptr<Data::UniqueGift> _gift;
const Fn<void(not_null<PeerData*>)> _choose;
ExportOption _exportOption;
};
[[nodiscard]] ExportOption MakeExportOption(
not_null<Window::SessionController*> window,
TimeId when) {
const auto activate = [=] {
const auto now = base::unixtime::now();
const auto left = (when > now) ? (when - now) : 0;
const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0;
window->show(Ui::MakeInformBox({
.text = (!hours
? tr::lng_gift_transfer_unlocks_update_about()
: tr::lng_gift_transfer_unlocks_about(
lt_when,
((hours >= 24)
? tr::lng_gift_transfer_unlocks_when_days(
lt_count,
rpl::single((hours / 24) * 1.))
: tr::lng_gift_transfer_unlocks_when_hours(
lt_count,
rpl::single(hours * 1.))))),
.title = (!hours
? tr::lng_gift_transfer_unlocks_update_title()
: tr::lng_gift_transfer_unlocks_title()),
}));
};
class ExportRow final : public PeerListRow {
public:
explicit ExportRow(TimeId when)
: PeerListRow(Data::FakePeerIdForJustName("ton-export").value) {
const auto now = base::unixtime::now();
_available = (when <= now);
if (const auto left = when - now; left > 0) {
const auto hours = std::max((left + 1800) / 3600, 1);
const auto days = hours / 24;
setCustomStatus(days
? tr::lng_gift_transfer_unlocks_days(
tr::now,
lt_count,
days)
: tr::lng_gift_transfer_unlocks_hours(
tr::now,
lt_count,
hours));
}
}
QString generateName() override {
return tr::lng_gift_transfer_via_blockchain(tr::now);
}
QString generateShortName() override {
return generateName();
}
auto generatePaintUserpicCallback(bool forceRound)
-> PaintRoundImageCallback override {
return [=](
Painter &p,
int x,
int y,
int outerWidth,
int size) mutable {
Ui::EmptyUserpic::PaintCurrency(p, x, y, outerWidth, size);
};
}
const style::PeerListItem &computeSt(
const style::PeerListItem &st) const {
return _available ? st::recentPeersSpecialName : st;
}
private:
bool _available = false;
};
class ExportController final : public PeerListController {
public:
ExportController(
not_null<Main::Session*> session,
TimeId when,
Fn<void()> activate)
: _session(session)
, _when(when)
, _activate(std::move(activate)) {
}
void prepare() override {
delegate()->peerListAppendRow(
std::make_unique<ExportRow>(_when));
delegate()->peerListRefreshRows();
}
void loadMoreRows() override {
}
void rowClicked(not_null<PeerListRow*> row) override {
_activate();
}
Main::Session &session() const override {
return *_session;
}
private:
const not_null<Main::Session*> _session;
TimeId _when = 0;
Fn<void()> _activate;
};
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
const auto container = result.data();
Ui::AddSkip(container);
const auto delegate = container->lifetime().make_state<
PeerListContentDelegateSimple
>();
const auto controller = container->lifetime().make_state<
ExportController
>(&window->session(), when, activate);
controller->setStyleOverrides(&st::peerListSingleRow);
const auto content = container->add(object_ptr<PeerListContent>(
container,
controller));
delegate->setContent(content);
controller->setDelegate(delegate);
Ui::AddSkip(container);
container->add(CreatePeerListSectionSubtitle(
container,
tr::lng_contacts_header()));
const auto overrideKey = [=](int direction, int from, int to) {
if (!content->isVisible()) {
return false;
} else if (direction > 0 && from < 0 && to >= 0) {
if (content->hasSelection()) {
const auto was = content->selectedIndex();
const auto now = content->selectSkip(1).reallyMovedTo;
if (was != now) {
return true;
}
content->clearSelection();
} else {
content->selectSkip(1);
return true;
}
} else if (direction < 0 && to < 0) {
if (!content->hasSelection()) {
content->selectLast();
} else if (from >= 0 || content->hasSelection()) {
content->selectSkip(-1);
}
}
return false;
};
return {
.content = std::move(result),
.overrideKey = overrideKey,
.activate = activate,
};
}
Controller::Controller(
not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift,
Fn<void(not_null<PeerData*>)> choose)
: ContactsBoxController(&window->session())
, _gift(std::move(gift))
, _choose(std::move(choose)) {
if (const auto when = _gift->exportAt) {
_exportOption = MakeExportOption(window, when);
}
if (_exportOption.content) {
setStyleOverrides(&st::peerListSmallSkips);
}
}
void Controller::noSearchSubmit() {
if (const auto onstack = _exportOption.activate) {
onstack();
}
}
bool Controller::overrideKeyboardNavigation(
int direction,
int fromIndex,
int toIndex) {
return _exportOption.overrideKey
&& _exportOption.overrideKey(direction, fromIndex, toIndex);
}
void Controller::prepareViewHook() {
delegate()->peerListSetTitle(tr::lng_gift_transfer_title(
lt_name,
rpl::single(UniqueGiftName(*_gift))));
setupExportOption();
}
void Controller::setupExportOption() {
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
}
std::unique_ptr<PeerListRow> Controller::createRow(
not_null<UserData*> user) {
if (user->isSelf()
|| user->isBot()
|| user->isServiceUser()
|| user->isInaccessible()) {
return nullptr;
}
return ContactsBoxController::createRow(user);
}
void Controller::rowClicked(not_null<PeerListRow*> row) {
_choose(row->peer());
}
void TransferGift(
not_null<Window::SessionController*> window,
not_null<PeerData*> to,
std::shared_ptr<Data::UniqueGift> gift,
MsgId messageId,
Fn<void(Payments::CheckoutResult)> done) {
Expects(to->isUser());
const auto session = &window->session();
const auto weak = base::make_weak(window);
if (gift->starsForTransfer <= 0) {
session->api().request(MTPpayments_TransferStarGift(
MTP_int(messageId.bare),
to->asUser()->inputUser
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
done(Payments::CheckoutResult::Paid);
}).fail([=](const MTP::Error &error) {
if (const auto strong = weak.get()) {
strong->showToast(error.type());
}
done(Payments::CheckoutResult::Failed);
}).send();
return;
}
Ui::RequestStarsFormAndSubmit(
window,
MTP_inputInvoiceStarGiftTransfer(
MTP_int(messageId.bare),
to->asUser()->inputUser),
std::move(done));
}
void ShowTransferToBox(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
std::shared_ptr<Data::UniqueGift> gift,
MsgId msgId) {
const auto stars = gift->starsForTransfer;
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_gift_transfer_title(
lt_name,
rpl::single(UniqueGiftName(*gift))));
auto transfer = (stars > 0)
? tr::lng_gift_transfer_button_for(
lt_price,
tr::lng_action_gift_for_stars(
lt_count,
rpl::single(stars * 1.)))
: tr::lng_gift_transfer_button();
struct State {
bool sent = false;
};
const auto state = std::make_shared<State>();
auto callback = [=] {
if (state->sent) {
return;
}
state->sent = true;
const auto weak = Ui::MakeWeak(box);
const auto done = [=](Payments::CheckoutResult result) {
if (result != Payments::CheckoutResult::Paid) {
state->sent = false;
} else {
controller->showPeerHistory(peer);
if (const auto strong = weak.data()) {
strong->closeBox();
}
}
};
TransferGift(controller, peer, gift, msgId, done);
};
Ui::ConfirmBox(box, {
.text = (stars > 0)
? tr::lng_gift_transfer_sure_for(
lt_name,
rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
lt_recipient,
rpl::single(Ui::Text::Bold(peer->shortName())),
lt_price,
tr::lng_action_gift_for_stars(
lt_count,
rpl::single(stars * 1.),
Ui::Text::Bold),
Ui::Text::WithEntities)
: tr::lng_gift_transfer_sure(
lt_name,
rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
lt_recipient,
rpl::single(Ui::Text::Bold(peer->shortName())),
Ui::Text::WithEntities),
.confirmed = std::move(callback),
.confirmText = std::move(transfer),
});
}));
}
} // namespace
void ShowTransferGiftBox(
not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift,
MsgId msgId) {
auto controller = std::make_unique<Controller>(
window,
gift,
[=](not_null<PeerData*> peer) {
ShowTransferToBox(window, peer, gift, msgId);
});
const auto controllerRaw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
box->noSearchSubmits() | rpl::start_with_next([=] {
controllerRaw->noSearchSubmit();
}, box->lifetime());
};
window->show(
Box<PeerListBox>(std::move(controller), std::move(initBox)),
Ui::LayerOption::KeepOther);
}

View file

@ -0,0 +1,21 @@
/*
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 Window {
class SessionController;
} // namespace Window
namespace Data {
struct UniqueGift;
} // namespace Data
void ShowTransferGiftBox(
not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift,
MsgId msgId);

View file

@ -84,6 +84,7 @@ struct CreditsHistoryEntry final {
bool converted : 1 = false;
bool anonymous : 1 = false;
bool stargift : 1 = false;
bool giftTransferred : 1 = false;
bool savedToProfile : 1 = false;
bool fromGiftsList : 1 = false;
bool soldOutInfo : 1 = false;

View file

@ -132,7 +132,6 @@ enum class GiftType : uchar {
Premium, // count - months
Credits, // count - credits
StarGift, // count - stars
StarGiftUpgrade,
};
struct GiftCode {
@ -151,11 +150,14 @@ struct GiftCode {
int count = 0;
GiftType type = GiftType::Premium;
bool viaGiveaway : 1 = false;
bool transferred : 1 = false;
bool upgradable : 1 = false;
bool unclaimed : 1 = false;
bool anonymous : 1 = false;
bool converted : 1 = false;
bool upgraded : 1 = false;
bool refunded : 1 = false;
bool upgrade : 1 = false;
bool saved : 1 = false;
};

View file

@ -38,8 +38,10 @@ struct UniqueGiftOriginalDetails {
struct UniqueGift {
QString title;
int number = 0;
PeerId ownerId = 0;
int number = 0;
int starsForTransfer = -1;
TimeId exportAt = 0;
UniqueGiftModel model;
UniqueGiftPattern pattern;
UniqueGiftBackdrop backdrop;

View file

@ -5463,13 +5463,17 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
const auto isSelf = _from->isSelf();
const auto peer = isSelf ? _history->peer : _from;
result.links.push_back(peer->createOpenLink());
result.text = (isSelf
? tr::lng_action_gift_upgraded_mine
: tr::lng_action_gift_upgraded)(
tr::now,
lt_user,
Ui::Text::Link(peer->shortName(), 1), // Link 1.
Ui::Text::WithEntities);
result.text = (action.is_upgrade()
? (isSelf
? tr::lng_action_gift_upgraded_mine
: tr::lng_action_gift_upgraded)
: (isSelf
? tr::lng_action_gift_transferred_mine
: tr::lng_action_gift_transferred))(
tr::now,
lt_user,
Ui::Text::Link(peer->shortName(), 1), // Link 1.
Ui::Text::WithEntities);
return result;
};
@ -5683,7 +5687,10 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
}, [&](const MTPDmessageActionStarGiftUnique &data) {
using Fields = Data::GiftCode;
auto fields = Fields{
.type = Data::GiftType::StarGiftUpgrade,
.type = Data::GiftType::StarGift,
.transferred = data.is_transferred(),
.refunded = data.is_refunded(),
.upgrade = data.is_upgrade(),
.saved = data.is_saved(),
};
if (auto gift = Api::FromTL(&history()->session(), data.vgift())) {
@ -5692,7 +5699,12 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
fields.limitedCount = gift->limitedCount;
fields.limitedLeft = gift->limitedLeft;
fields.count = gift->stars;
fields.unique = gift->unique;
fields.unique = std::move(gift->unique);
if (const auto unique = fields.unique.get()) {
unique->starsForTransfer
= data.vtransfer_stars().value_or(-1);
unique->exportAt = data.vcan_export_at().value_or_empty();
}
}
_media = std::make_unique<Data::MediaGiftBox>(
this,

View file

@ -235,7 +235,7 @@ void PremiumGift::draw(
QString PremiumGift::cornerTagText() {
if (_data.unique) {
return tr::lng_gift_limited_of_one(tr::now);
return tr::lng_gift_collectible_tag(tr::now);
} else if (const auto count = _data.limitedCount) {
return (count == 1)
? tr::lng_gift_limited_of_one(tr::now)
@ -292,12 +292,11 @@ bool PremiumGift::gift() const {
}
bool PremiumGift::starGift() const {
return (_data.type == Data::GiftType::StarGift)
|| (_data.type == Data::GiftType::StarGiftUpgrade);
return (_data.type == Data::GiftType::StarGift);
}
bool PremiumGift::starGiftUpgrade() const {
return (_data.type == Data::GiftType::StarGiftUpgrade);
return (_data.type == Data::GiftType::StarGift) && _data.upgrade;
}
bool PremiumGift::creditsPrize() const {

View file

@ -400,6 +400,12 @@ auto GenerateUniqueGiftMedia(
st));
};
const auto item = parent->data();
const auto media = item->media();
const auto fields = media ? media->gift() : nullptr;
const auto upgrade = fields && fields->upgrade;
const auto outgoing = upgrade ? !item->out() : item->out();
const auto white = QColor(255, 255, 255);
const auto sticker = [=] {
using Tag = ChatHelpers::StickerLottieSize;
@ -415,7 +421,7 @@ auto GenerateUniqueGiftMedia(
sticker,
st::chatUniqueStickerPadding));
pushText(
Ui::Text::Bold((!parent->data()->out()
Ui::Text::Bold((outgoing
? tr::lng_action_gift_sent_subtitle
: tr::lng_action_gift_got_subtitle)(
tr::now,
@ -430,7 +436,7 @@ auto GenerateUniqueGiftMedia(
gift->backdrop.textColor,
st::chatUniqueTextPadding);
const auto withButton = parent->data()->out();
const auto withButton = !outgoing;
auto attributes = std::vector<AttributeTable::Entry>{
{ tr::lng_gift_unique_model(tr::now), gift->model.name },

View file

@ -63,6 +63,10 @@ QImage IconCurrencyColored(
return image;
}
QByteArray CurrencySvgColored(const QColor &c) {
return CurrencySvg(c);
}
QImage MenuIconCurrency(const QSize &size) {
auto image = QImage(
size * style::DevicePixelRatio(),

View file

@ -12,6 +12,7 @@ namespace Ui::Earn {
[[nodiscard]] QImage IconCurrencyColored(
const style::font &font,
const QColor &c);
[[nodiscard]] QByteArray CurrencySvgColored(const QColor &c);
[[nodiscard]] QImage MenuIconCurrency(const QSize &size);
[[nodiscard]] QImage MenuIconCredits();

View file

@ -1318,7 +1318,7 @@ void ReceiptCreditsBox(
.cost = e.starsUpgradedBySender ? 0 : e.starsToUpgrade,
.canAddSender = !e.anonymous,
.canAddComment = !e.anonymous && e.hasGiftComment,
});
});
}
};
const auto canUpgrade = e.stargiftId
@ -1369,7 +1369,8 @@ void ReceiptCreditsBox(
}
});
};
const auto canToggle = canConvert || couldConvert || nonConvertible;
const auto canToggle = (canConvert || couldConvert || nonConvertible)
&& !e.giftTransferred;
AddStarGiftTable(
controller,
@ -1665,7 +1666,9 @@ 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,
.bareGiftOwnerId = (data.unique
? data.unique->ownerId.value
: item->history()->session().userPeerId().value),
.stargiftId = data.stargiftId,
.uniqueGift = data.unique,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
@ -1677,6 +1680,7 @@ void StarGiftViewBox(
.converted = data.converted,
.anonymous = data.anonymous,
.stargift = true,
.giftTransferred = data.transferred,
.savedToProfile = data.saved,
.canUpgradeGift = data.upgradable,
.hasGiftComment = !data.message.empty(),

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "ui/empty_userpic.h"
#include "info/channel_statistics/earn/earn_icons.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/animation_value.h"
#include "ui/emoji_config.h"
@ -17,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_widgets.h" // style::IconButton
#include "styles/style_info.h" // st::topBarCall
#include <QtSvg/QSvgRenderer>
namespace Ui {
namespace {
@ -183,6 +186,18 @@ void PaintMyNotesInner(
fg);
}
void PaintCurrencyInner(
QPainter &p,
int x,
int y,
int size,
const style::color &fg) {
auto svg = QSvgRenderer(Ui::Earn::CurrencySvgColored(fg->c));
const auto skip = size / 5;
svg.render(&p, QRect(x, y, size, size).marginsRemoved(
{ skip, skip, skip, skip }));
}
void PaintExternalMessagesInner(
QPainter &p,
int x,
@ -507,6 +522,45 @@ QImage EmptyUserpic::GenerateMyNotes(int size) {
});
}
void EmptyUserpic::PaintCurrency(
QPainter &p,
int x,
int y,
int outerWidth,
int size) {
auto bg = QLinearGradient(x, y, x, y + size);
bg.setStops({
{ 0., st::historyPeerSavedMessagesBg->c },
{ 1., st::historyPeerSavedMessagesBg2->c }
});
const auto &fg = st::historyPeerUserpicFg;
PaintCurrency(p, x, y, outerWidth, size, QBrush(bg), fg);
}
void EmptyUserpic::PaintCurrency(
QPainter &p,
int x,
int y,
int outerWidth,
int size,
QBrush bg,
const style::color &fg) {
x = style::RightToLeft() ? (outerWidth - x - size) : x;
PainterHighQualityEnabler hq(p);
p.setBrush(bg);
p.setPen(Qt::NoPen);
p.drawEllipse(x, y, size, size);
PaintCurrencyInner(p, x, y, size, fg);
}
QImage EmptyUserpic::GenerateCurrency(int size) {
return Generate(size, [&](QPainter &p) {
PaintCurrency(p, 0, 0, size, size);
});
}
std::pair<uint64, uint64> EmptyUserpic::uniqueKey() const {
const auto first = (uint64(0xFFFFFFFFU) << 32)
| anim::getPremultiplied(_colors.color1->c);

View file

@ -113,6 +113,22 @@ public:
const style::color &fg);
[[nodiscard]] static QImage GenerateMyNotes(int size);
static void PaintCurrency(
QPainter &p,
int x,
int y,
int outerWidth,
int size);
static void PaintCurrency(
QPainter &p,
int x,
int y,
int outerWidth,
int size,
QBrush bg,
const style::color &fg);
[[nodiscard]] static QImage GenerateCurrency(int size);
~EmptyUserpic();
private: