diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d225d9e38..736551fc9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3015,6 +3015,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_limited_of_count" = "1 of {amount}"; "lng_gift_anonymous_hint" = "Only you can see the sender's name."; "lng_gift_availability" = "Availability"; +"lng_gift_from_hidden" = "Hidden User"; "lng_gift_availability_left#one" = "{count} of {amount} left"; "lng_gift_availability_left#other" = "{count} of {amount} left"; "lng_gift_availability_none" = "None of {amount} left"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 28f467fe6..84ce45783 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -780,8 +780,9 @@ std::optional FromTL( } std::optional FromTL( - not_null session, + not_null to, const MTPuserStarGift &gift) { + const auto session = &to->session(); const auto &data = gift.data(); auto parsed = FromTL(session, data.vgift()); if (!parsed) { @@ -802,8 +803,10 @@ std::optional FromTL( ? peerFromUser(data.vfrom_id()->v) : PeerId()), .messageId = data.vmsg_id().value_or_empty(), + .date = data.vdate().v, .anonymous = data.is_name_hidden(), .hidden = data.is_unsaved(), + .mine = to->isSelf(), }; } diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 97d8c5013..805b68e03 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -88,8 +88,10 @@ struct UserStarGift { int64 convertStars = 0; PeerId fromId = 0; MsgId messageId = 0; + TimeId date = 0; bool anonymous = false; bool hidden = false; + bool mine = false; }; class Premium final { @@ -278,7 +280,7 @@ enum class RequirePremiumState { not_null session, const MTPstarGift &gift); [[nodiscard]] std::optional FromTL( - not_null session, + not_null to, const MTPuserStarGift &gift); } // namespace Api diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp index d93749314..c0db5d248 100644 --- a/Telegram/SourceFiles/boxes/gift_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp @@ -481,7 +481,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { .stars = gift.stars, .convertStars = gift.convertStars, .document = gift.document, - .limited = (gift.limitedCount > 0), + .limitedCount = gift.limitedCount, }); } auto &map = Map[session]; @@ -554,7 +554,7 @@ struct GiftPriceTabs { auto sameKey = 0; for (const auto &gift : gifts) { if (same) { - const auto key = gift.stars * (gift.limited ? -1 : 1); + const auto key = gift.stars * (gift.limitedCount ? -1 : 1); if (!sameKey) { sameKey = key; } else if (sameKey != key) { @@ -562,7 +562,7 @@ struct GiftPriceTabs { } } - if (gift.limited + if (gift.limitedCount && (result.size() < 2 || result[1] != kPriceTabLimited)) { result.insert(begin(result) + 1, kPriceTabLimited); } @@ -1036,7 +1036,7 @@ void AddBlock( ) | rpl::map([=](std::vector &&gifts, int price) { gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) { return (price == kPriceTabLimited) - ? (!gift.limited) + ? (!gift.limitedCount) : (price && gift.stars != price); }), end(gifts)); return GiftsDescriptor{ diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 4873fcbcb..a44fdade5 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -793,6 +793,43 @@ void GiftsBox( return result; } +[[nodiscard]] object_ptr MakeHiddenPeerTableValue( + not_null parent, + not_null controller) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + const auto &st = st::giveawayGiftCodeUserpic; + raw->resize(raw->width(), st.photoSize); + + const auto userpic = Ui::CreateChild(raw); + const auto usize = st.photoSize; + userpic->resize(usize, usize); + userpic->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(userpic); + Ui::EmptyUserpic::PaintHiddenAuthor(p, 0, 0, usize, usize); + }, userpic->lifetime()); + + const auto label = Ui::CreateChild( + raw, + tr::lng_gift_from_hidden(), + st::giveawayGiftCodeValue); + raw->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto position = st::giveawayGiftCodeNamePosition; + label->resizeToNaturalWidth(width - position.x()); + label->moveToLeft(position.x(), position.y(), width); + const auto top = (raw->height() - userpic->height()) / 2; + userpic->moveToLeft(0, top, width); + }, label->lifetime()); + + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setTextColorOverride(st::windowFg->c); + + return result; +} + void AddTableRow( not_null table, rpl::producer label, @@ -1721,10 +1758,17 @@ void AddStarGiftTable( st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(entry.barePeerId); if (peerId) { - auto text = entry.in - ? tr::lng_credits_box_history_entry_peer_in() - : tr::lng_credits_box_history_entry_peer(); - AddTableRow(table, std::move(text), controller, peerId); + AddTableRow( + table, + tr::lng_credits_box_history_entry_peer_in(), + controller, + peerId); + } else { + AddTableRow( + table, + tr::lng_credits_box_history_entry_peer_in(), + MakeHiddenPeerTableValue(table, controller), + st::giveawayGiftCodePeerMargin); } if (!entry.date.isNull()) { AddTableRow( diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index c15f55b31..565ee6c7a 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -19,6 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_credits.h" @@ -39,7 +41,15 @@ GiftButton::GiftButton( , _delegate(delegate) { } -GiftButton::~GiftButton() = default; +GiftButton::~GiftButton() { + unsubscribe(); +} + +void GiftButton::unsubscribe() { + if (base::take(_subscribed)) { + _userpic->subscribeToUpdates(nullptr); + } +} void GiftButton::setDescriptor(const GiftDescriptor &descriptor) { if (_descriptor == descriptor) { @@ -48,6 +58,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor) { auto player = base::take(_player); _mediaLifetime.destroy(); _descriptor = descriptor; + unsubscribe(); v::match(descriptor, [&](const GiftTypePremium &data) { const auto months = data.months; const auto years = (months % 12) ? 0 : months / 12; @@ -66,12 +77,18 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor) { data.cost, data.currency, true)); + _userpic = nullptr; }, [&](const GiftTypeStars &data) { _price.setMarkedText( st::semiboldTextStyle, _delegate->star().append(QString::number(data.stars)), kMarkupTextOptions, _delegate->textContext()); + _userpic = !data.userpic + ? nullptr + : data.from + ? Ui::MakeUserpicThumbnail(data.from) + : Ui::MakeHiddenAuthorThumbnail(); }); if (const auto document = _delegate->lookupSticker(descriptor)) { setDocument(document); @@ -166,6 +183,16 @@ void GiftButton::paintEvent(QPaintEvent *e) { QRect(half, 0, 1, height)); } + if (_userpic) { + if (!_subscribed) { + _subscribed = true; + _userpic->subscribeToUpdates([=] { update(); }); + } + const auto image = _userpic->image(st::giftBoxUserpicSize); + const auto skip = st::giftBoxUserpicSkip; + p.drawImage(_extend.left() + skip, _extend.top() + skip, image); + } + if (_player && _player->ready()) { const auto paused = !isOver(); auto info = _player->frame( @@ -203,9 +230,18 @@ void GiftButton::paintEvent(QPaintEvent *e) { } return QString(); }, [&](const GiftTypeStars &data) { - if (data.limited) { + if (const auto count = data.limitedCount) { p.setBrush(st::windowActiveTextFg); - return tr::lng_gift_stars_limited(tr::now); + return !data.userpic + ? tr::lng_gift_stars_limited(tr::now) + : (count == 1) + ? tr::lng_gift_limited_of_one(tr::now) + : tr::lng_gift_limited_of_count( + tr::now, + lt_amount, + ((count % 1000) + ? Lang::FormatCountDecimal(count) + : Lang::FormatCountToShort(count).string)); } return QString(); }); diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index 42ec3ebf2..c53ce9503 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -14,6 +14,10 @@ namespace HistoryView { class StickerPlayer; } // namespace HistoryView +namespace Ui { +class DynamicImage; +} // namespace Ui + namespace Window { class SessionController; } // namespace Window @@ -37,8 +41,9 @@ struct GiftTypeStars { int64 convertStars = 0; DocumentData *document = nullptr; PeerData *from = nullptr; - bool limited = false; + int limitedCount = 0; bool userpic = false; + bool mine = false; [[nodiscard]] friend inline bool operator==( const GiftTypeStars&, @@ -77,11 +82,15 @@ private: void resizeEvent(QResizeEvent *e) override; void setDocument(not_null document); + void unsubscribe(); const not_null _delegate; GiftDescriptor _descriptor; Ui::Text::String _text; Ui::Text::String _price; + std::shared_ptr _userpic; + bool _subscribed = false; + QRect _button; QMargins _extend; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 871ff6200..a10942055 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -11,11 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "info/peer_gifts/info_peer_gifts_common.h" #include "info/info_controller.h" +#include "ui/layers/generic_box.h" #include "ui/ui_utility.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "mtproto/sender.h" #include "window/window_session_controller.h" +#include "settings/settings_credits_graphics.h" #include "styles/style_info.h" #include "styles/style_credits.h" // giftBoxPadding @@ -26,18 +28,19 @@ constexpr auto kPreloadPages = 2; constexpr auto kPerPage = 50; [[nodiscard]] GiftDescriptor DescriptorForGift( - not_null owner, + not_null to, const Api::UserStarGift &gift) { return GiftTypeStars{ .id = gift.gift.id, .stars = gift.gift.stars, .convertStars = gift.gift.convertStars, .document = gift.gift.document, - .from = ((gift.hidden || !gift.fromId) + .from = ((gift.anonymous || !gift.fromId) ? nullptr - : owner->peer(gift.fromId).get()), - .limited = (gift.gift.limitedCount > 0), + : to->owner().peer(gift.fromId).get()), + .limitedCount = gift.gift.limitedCount, .userpic = true, + .mine = to->isSelf(), }; } @@ -74,11 +77,12 @@ private: void loadMore(); void validateButtons(); + void showGift(int index); int resizeGetHeight(int width) override; + const not_null _window; Delegate _delegate; - const std::shared_ptr _show; not_null _controller; const not_null _user; std::vector _entries; @@ -107,8 +111,8 @@ InnerWidget::InnerWidget( not_null controller, not_null user) : RpWidget(parent) -, _delegate(controller->parentController()) -, _show(controller->uiShow()) +, _window(controller->parentController()) +, _delegate(_window) , _controller(controller) , _user(user) , _totalCount(_user->peerGiftsCount()) @@ -155,11 +159,10 @@ void InnerWidget::loadMore() { const auto owner = &_user->owner(); owner->processUsers(data.vusers()); - const auto session = &_show->session(); _entries.reserve(_entries.size() + data.vgifts().v.size()); for (const auto &gift : data.vgifts().v) { - if (auto parsed = Api::FromTL(session, gift)) { - auto descriptor = DescriptorForGift(owner, *parsed); + if (auto parsed = Api::FromTL(_user, gift)) { + auto descriptor = DescriptorForGift(_user, *parsed); _entries.push_back({ .gift = std::move(*parsed), .descriptor = std::move(descriptor), @@ -212,6 +215,9 @@ void InnerWidget::validateButtons() { return; } const auto &descriptor = _entries[index].descriptor; + const auto callback = [=] { + showGift(index); + }; const auto unused = ranges::find_if(_views, [&](const View &v) { return v.button && ((v.entry < fromRow * _perRow) @@ -219,18 +225,18 @@ void InnerWidget::validateButtons() { }); if (unused != end(_views)) { views.push_back(base::take(*unused)); - views.back().button->setDescriptor(descriptor); views.back().entry = index; - return; + } else { + auto button = std::make_unique(this, &_delegate); + button->show(); + views.push_back({ + .button = std::move(button), + .entry = index, + }); } - auto button = std::make_unique(this, &_delegate); - button->setDescriptor(descriptor); - button->show(); - views.push_back({ - .button = std::move(button), - .entry = index, - }); - }; + views.back().button->setDescriptor(descriptor); + views.back().button->setClickedCallback(callback); + }; for (auto j = fromRow; j != tillRow; ++j) { for (auto i = 0; i != _perRow; ++i) { const auto index = j * _perRow + i; @@ -249,6 +255,13 @@ void InnerWidget::validateButtons() { std::swap(_views, views); } +void InnerWidget::showGift(int index) { + _window->show(Box( + ::Settings::UserStarGiftBox, + _window, + _entries[index].gift)); +} + int InnerWidget::resizeGetHeight(int width) { const auto count = int(_entries.size()); const auto padding = st::giftBoxPadding; diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 96d2cf00a..a8be5e032 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_invite.h" #include "api/api_credits.h" #include "api/api_earn.h" +#include "api/api_premium.h" #include "apiwrap.h" #include "base/timer_rpl.h" #include "base/unixtime.h" @@ -730,10 +731,13 @@ void ReceiptCreditsBox( const auto item = controller->session().data().message( PeerId(e.barePeerId), MsgId(e.bareMsgId)); const auto isStarGift = (e.convertStars > 0); + const auto myStarGift = isStarGift && e.in; const auto starGiftSender = (isStarGift && item) ? item->history()->peer->asUser() : nullptr; - const auto canConvert = isStarGift && !e.converted && starGiftSender; + const auto canConvert = myStarGift + && !e.converted + && starGiftSender; box->setStyle(canConvert ? st::starGiftBox : st::giveawayGiftCodeBox); box->setNoContentMargin(true); @@ -857,6 +861,8 @@ void ReceiptCreditsBox( ? tr::lng_credits_box_history_entry_subscription(tr::now) : !e.title.isEmpty() ? e.title + : (isStarGift && !myStarGift) + ? tr::lng_gift_link_label_gift(tr::now) : e.gift ? tr::lng_credits_box_history_entry_gift_name(tr::now) : (peer && !e.reaction) @@ -935,7 +941,7 @@ void ReceiptCreditsBox( ? st::windowSubTextFg : e.pending ? st::creditsStroke - : e.in + : (e.in || isStarGift) ? st::boxTextFgGood : e.gift ? st::windowBoldFg @@ -990,7 +996,7 @@ void ReceiptCreditsBox( rpl::single(e.description), st::creditsBoxAbout))); } - if (isStarGift) { + if (myStarGift) { Ui::AddSkip(content); const auto about = box->addRow( object_ptr>( @@ -1015,6 +1021,7 @@ void ReceiptCreditsBox( tr::lng_paid_about_link_url(tr::now)); return false; }); + } else if (isStarGift) { } else if (e.gift || isPrize) { Ui::AddSkip(content); const auto arrow = Ui::Text::SingleCustomEmoji( @@ -1074,7 +1081,7 @@ void ReceiptCreditsBox( tr::lng_credits_box_out_about_link(tr::now)), Ui::Text::WithEntities), st::creditsBoxAboutDivider))); - } else if (e.anonymous) { + } else if (myStarGift && e.anonymous) { box->addRow(object_ptr>( box, object_ptr( @@ -1336,6 +1343,35 @@ void CreditsPrizeBox( Data::SubscriptionEntry()); } +void UserStarGiftBox( + not_null box, + not_null controller, + const Api::UserStarGift &data) { + Settings::ReceiptCreditsBox( + box, + controller, + Data::CreditsHistoryEntry{ + .description = data.message, + .date = base::unixtime::parse(data.date), + .credits = uint64(data.gift.stars), + .bareMsgId = uint64(data.messageId.bare), + .barePeerId = data.fromId.value, + .bareGiftStickerId = (data.gift.document + ? data.gift.document->id + : 0), + .peerType = Data::CreditsHistoryEntry::PeerType::Peer, + .limitedCount = data.gift.limitedCount, + .limitedLeft = data.gift.limitedLeft, + .convertStars = int(data.gift.convertStars), + .converted = false, + .anonymous = data.anonymous, + .savedToProfile = !data.hidden, + .in = data.mine, + .gift = true, + }, + Data::SubscriptionEntry()); +} + void StarGiftViewBox( not_null box, not_null controller, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index aca4691cb..37b14c949 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -12,6 +12,10 @@ class object_ptr; class PeerData; +namespace Api { +struct UserStarGift; +} // namespace Api + namespace Data { struct Boost; struct CreditsHistoryEntry; @@ -92,6 +96,10 @@ void CreditsPrizeBox( not_null controller, const Data::GiftCode &data, TimeId date); +void UserStarGiftBox( + not_null box, + not_null controller, + const Api::UserStarGift &data); void StarGiftViewBox( not_null box, not_null controller, diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 6f7e3bb07..ff3d718b3 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -103,6 +103,8 @@ giftBoxPreviewTextPadding: margins(12px, 4px, 12px, 24px); giftBoxStickerTop: 0px; giftBoxStickerStarTop: 24px; giftBoxStickerSize: size(80px, 80px); +giftBoxUserpicSize: 24px; +giftBoxUserpicSkip: 2px; giftBoxTextField: InputField(defaultInputField) { textBg: transparent; textMargins: margins(2px, 0px, 32px, 0px);