diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 05e9e228e..1e0365f37 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 97c8501a7..647d08302 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 338d7bc05..682f34cde 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -806,8 +806,8 @@ std::optional FromTL( .id = uint64(data.vid().v), .unique = std::make_shared(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 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), diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 0bed9cf1a..e06508133 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -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([=] {}) : 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([=] { + ShowTransferGiftBox( + controller->parentController(), + entry.uniqueGift, + MsgId(entry.bareMsgId)); + }) : nullptr; AddTableRow( table, tr::lng_gift_unique_owner(), diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index c0cc7442d..b84466c1d 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -1129,11 +1129,10 @@ void ShowGiftUpgradedToast( } } -void SendUpgradeRequest( +void SendStarsFormRequest( not_null controller, Settings::SmallBalanceResult result, uint64 formId, - int stars, MTPInputInvoice invoice, Fn 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 box) { }); } +void RequestStarsFormAndSubmit( + not_null window, + MTPInputInvoice invoice, + Fn 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 diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index 70d1ae8bf..d8789bb39 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -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 box); +void RequestStarsFormAndSubmit( + not_null window, + MTPInputInvoice invoice, + Fn done); + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/transfer_gift_box.cpp b/Telegram/SourceFiles/boxes/transfer_gift_box.cpp new file mode 100644 index 000000000..190362d09 --- /dev/null +++ b/Telegram/SourceFiles/boxes/transfer_gift_box.cpp @@ -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 content = { nullptr }; + Fn overrideKey; + Fn activate; +}; + +class Controller final : public ContactsBoxController { +public: + Controller( + not_null window, + std::shared_ptr gift, + Fn)> choose); + + void noSearchSubmit(); + +private: + void prepareViewHook() override; + void setupExportOption(); + + bool overrideKeyboardNavigation( + int direction, + int fromIndex, + int toIndex) override; + + std::unique_ptr createRow( + not_null user) override; + void rowClicked(not_null row) override; + + const std::shared_ptr _gift; + const Fn)> _choose; + ExportOption _exportOption; + +}; + +[[nodiscard]] ExportOption MakeExportOption( + not_null 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 session, + TimeId when, + Fn activate) + : _session(session) + , _when(when) + , _activate(std::move(activate)) { + } + + void prepare() override { + delegate()->peerListAppendRow( + std::make_unique(_when)); + delegate()->peerListRefreshRows(); + } + void loadMoreRows() override { + } + void rowClicked(not_null row) override { + _activate(); + } + Main::Session &session() const override { + return *_session; + } + + private: + const not_null _session; + TimeId _when = 0; + Fn _activate; + + }; + + auto result = object_ptr((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( + 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, + std::shared_ptr gift, + Fn)> 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 Controller::createRow( + not_null user) { + if (user->isSelf() + || user->isBot() + || user->isServiceUser() + || user->isInaccessible()) { + return nullptr; + } + return ContactsBoxController::createRow(user); +} + +void Controller::rowClicked(not_null row) { + _choose(row->peer()); +} + +void TransferGift( + not_null window, + not_null to, + std::shared_ptr gift, + MsgId messageId, + Fn 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 controller, + not_null peer, + std::shared_ptr gift, + MsgId msgId) { + const auto stars = gift->starsForTransfer; + controller->show(Box([=](not_null 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(); + 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, + std::shared_ptr gift, + MsgId msgId) { + auto controller = std::make_unique( + window, + gift, + [=](not_null peer) { + ShowTransferToBox(window, peer, gift, msgId); + }); + const auto controllerRaw = controller.get(); + auto initBox = [=](not_null box) { + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + + + box->noSearchSubmits() | rpl::start_with_next([=] { + controllerRaw->noSearchSubmit(); + }, box->lifetime()); + }; + window->show( + Box(std::move(controller), std::move(initBox)), + Ui::LayerOption::KeepOther); +} diff --git a/Telegram/SourceFiles/boxes/transfer_gift_box.h b/Telegram/SourceFiles/boxes/transfer_gift_box.h new file mode 100644 index 000000000..2b5ee2240 --- /dev/null +++ b/Telegram/SourceFiles/boxes/transfer_gift_box.h @@ -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, + std::shared_ptr gift, + MsgId msgId); diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 5ab71ef7b..567eb9478 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -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; diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 428717ca1..c2cd05699 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -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; }; diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h index 733b54143..3f7578de1 100644 --- a/Telegram/SourceFiles/data/data_star_gift.h +++ b/Telegram/SourceFiles/data/data_star_gift.h @@ -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; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 2306eb231..4ad4081c9 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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( this, diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp index 950ab283b..f3a1d0fd2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -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 { diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp index efb9fc573..0fe800f4b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp @@ -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{ { tr::lng_gift_unique_model(tr::now), gift->model.name }, diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp index b6c3ad85e..0455610fd 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp @@ -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(), diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.h b/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.h index ccc2ca7f8..fbdb8160d 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.h +++ b/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.h @@ -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(); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 0ef0c2ffa..51984ac8b 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -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(), diff --git a/Telegram/SourceFiles/ui/empty_userpic.cpp b/Telegram/SourceFiles/ui/empty_userpic.cpp index 1eebf7a11..ea38baa05 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.cpp +++ b/Telegram/SourceFiles/ui/empty_userpic.cpp @@ -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 + 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 EmptyUserpic::uniqueKey() const { const auto first = (uint64(0xFFFFFFFFU) << 32) | anim::getPremultiplied(_colors.color1->c); diff --git a/Telegram/SourceFiles/ui/empty_userpic.h b/Telegram/SourceFiles/ui/empty_userpic.h index 7d293db54..fba1fd88a 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.h +++ b/Telegram/SourceFiles/ui/empty_userpic.h @@ -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: