From 232077b9193abb1a1c9d0cdbaa43c1916442b914 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Jan 2025 18:38:40 +0400 Subject: [PATCH] Allow wearing unique gifts as status. --- Telegram/Resources/langs/lang.strings | 11 ++ Telegram/SourceFiles/api/api_premium.cpp | 1 + Telegram/SourceFiles/boxes/star_gift_box.cpp | 172 ++++++++++++++++++ Telegram/SourceFiles/boxes/star_gift_box.h | 13 ++ .../SourceFiles/data/data_emoji_statuses.cpp | 22 +++ .../SourceFiles/data/data_emoji_statuses.h | 2 + Telegram/SourceFiles/data/data_star_gift.h | 1 + .../settings/settings_credits_graphics.cpp | 19 ++ Telegram/SourceFiles/ui/effects/credits.style | 2 + 9 files changed, 243 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 81f4f6352..693cf00d8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3345,6 +3345,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_transfer_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?"; "lng_gift_transfer_button" = "Transfer"; "lng_gift_transfer_button_for" = "Transfer for {price}"; +"lng_gift_transfer_wear" = "Wear"; +"lng_gift_transfer_take_off" = "Take Off"; +"lng_gift_wear_title" = "Wear {name}"; +"lng_gift_wear_about" = "and get these benefits:"; +"lng_gift_wear_badge_title" = "Radiant Badge"; +"lng_gift_wear_badge_about" = "The glittering icon of this item will be displayed next to your name."; +"lng_gift_wear_proof_title" = "Proof of Ownership"; +"lng_gift_wear_proof_about" = "Clicking the icon of this item next to your name will show its info and owner."; +"lng_gift_wear_start" = "Start Wearing"; +"lng_gift_wear_start_toast" = "You put on {name}"; +"lng_gift_wear_end_toast" = "You took off {name}"; "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index c18beafa5..b689b2d22 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -805,6 +805,7 @@ std::optional FromTL( auto result = Data::StarGift{ .id = uint64(data.vid().v), .unique = std::make_shared(Data::UniqueGift{ + .id = data.vid().v, .slug = qs(data.vslug()), .title = qs(data.vtitle()), .ownerName = qs(data.vowner_name().value_or_empty()), diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 158f322e8..07da724b0 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_credits.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_emoji_statuses.h" #include "data/data_file_origin.h" #include "data/data_session.h" #include "data/data_user.h" @@ -2118,6 +2119,177 @@ void AddUniqueGiftCover( }, cover->lifetime()); } +void AddWearGiftCover( + not_null container, + const Data::UniqueGift &data, + not_null peer) { + const auto cover = container->add(object_ptr(container)); + + const auto title = CreateChild( + cover, + rpl::single(peer->name()), + st::uniqueGiftTitle); + title->setTextColorOverride(QColor(255, 255, 255)); + const auto subtitle = CreateChild( + cover, + tr::lng_status_online(), + st::uniqueGiftSubtitle); + subtitle->setTextColorOverride(data.backdrop.textColor); + + struct State { + QImage gradient; + Data::UniqueGift gift; + Ui::PeerUserpicView view; + std::unique_ptr emoji; + base::flat_map emojis; + rpl::lifetime lifetime; + }; + const auto state = cover->lifetime().make_state(State{ + .gift = data, + }); + state->emoji = peer->owner().customEmojiManager().create( + state->gift.pattern.document, + [=] { cover->update(); }, + Data::CustomEmojiSizeTag::Large); + + cover->widthValue() | rpl::start_with_next([=](int width) { + const auto skip = st::uniqueGiftBottom; + if (width <= 3 * skip) { + return; + } + const auto available = width - 2 * skip; + title->resizeToWidth(available); + title->moveToLeft(skip, st::uniqueGiftTitleTop); + + subtitle->resizeToWidth(available); + subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop); + + cover->resize(width, subtitle->y() + subtitle->height() + skip); + }, cover->lifetime()); + + cover->paintRequest() | rpl::start_with_next([=] { + auto p = Painter(cover); + + const auto width = cover->width(); + const auto pointsHeight = st::uniqueGiftSubtitleTop; + const auto ratio = style::DevicePixelRatio(); + if (state->gradient.size() != cover->size() * ratio) { + state->gradient = CreateGradient(cover->size(), state->gift); + } + p.drawImage(0, 0, state->gradient); + + PaintPoints( + p, + PatternPoints(), + state->emojis, + state->emoji.get(), + state->gift, + QRect(0, 0, width, pointsHeight), + 1.); + + peer->paintUserpic( + p, + state->view, + (width - st::uniqueGiftUserpicSize) / 2, + st::uniqueGiftUserpicTop, + st::uniqueGiftUserpicSize); + }, cover->lifetime()); +} + +void ShowUniqueGiftWearBox( + std::shared_ptr show, + const Data::UniqueGift &gift, + Settings::CreditsEntryBoxStyleOverrides st) { + show->show(Box([=](not_null box) { + box->setNoContentMargin(true); + + const auto content = box->verticalLayout(); + AddWearGiftCover(content, gift, show->session().user()); + + AddSkip(content, st::defaultVerticalListSkip * 2); + + const auto infoRow = [&]( + rpl::producer title, + rpl::producer text, + not_null icon) { + auto raw = content->add( + object_ptr(content)); + raw->add( + object_ptr( + raw, + std::move(title) | Ui::Text::ToBold(), + st::defaultFlatLabel), + st::settingsPremiumRowTitlePadding); + raw->add( + object_ptr( + raw, + std::move(text), + st::boxDividerLabel), + st::settingsPremiumRowAboutPadding); + object_ptr( + raw, + *icon, + st::starrefInfoIconPosition); + }; + + content->add( + object_ptr( + content, + tr::lng_gift_wear_title( + lt_name, + rpl::single(UniqueGiftName(gift))), + st::uniqueGiftTitle), + st::settingsPremiumRowTitlePadding); + content->add( + object_ptr( + content, + tr::lng_gift_wear_about(), + st::uniqueGiftSubtitle), + st::settingsPremiumRowTitlePadding); + //"lng_gift_wear_title" = "Wear {name}"; + //"lng_gift_wear_about" = "and get these benefits:"; + + infoRow( + tr::lng_gift_wear_badge_title(), + tr::lng_gift_wear_badge_about(), + &st::menuIconUnique); + //infoRow( + // tr::lng_gift_wear_design_title(), + // tr::lng_gift_wear_design_about(), + // &st::menuIconUniqueProfile); + infoRow( + tr::lng_gift_wear_proof_title(), + tr::lng_gift_wear_proof_about(), + &st::menuIconTradable); // todo collectibles + + box->setStyle(st::upgradeGiftBox); + + const auto button = box->addButton(tr::lng_gift_wear_start(), [=] { + show->session().data().emojiStatuses().set( + show->session().user(), + show->session().data().emojiStatuses().fromUniqueGift(gift)); + box->closeBox(); + show->showToast(tr::lng_gift_wear_start_toast( + tr::now, + lt_name, + UniqueGiftName(gift))); + }); + rpl::combine( + box->widthValue(), + button->widthValue() + ) | rpl::start_with_next([=](int outer, int inner) { + const auto padding = st::giftBox.buttonPadding; + const auto wanted = outer - padding.left() - padding.right(); + if (inner != wanted) { + button->resizeToWidth(wanted); + button->moveToLeft(padding.left(), padding.top()); + } + }, box->lifetime()); + + AddUniqueCloseButton(box, st); + })); +} + struct UpgradeArgs : StarGiftUpgradeArgs { std::vector models; std::vector patterns; diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index 59b201e66..8811f16c0 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Data { struct UniqueGift; struct GiftCode; @@ -46,6 +50,15 @@ void AddUniqueGiftCover( not_null container, rpl::producer data, rpl::producer subtitleOverride = nullptr); +void AddWearGiftCover( + not_null container, + const Data::UniqueGift &data, + not_null peer); + +void ShowUniqueGiftWearBox( + std::shared_ptr show, + const Data::UniqueGift &gift, + Settings::CreditsEntryBoxStyleOverrides st); struct PatternPoint { QPointF position; diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.cpp b/Telegram/SourceFiles/data/data_emoji_statuses.cpp index f6d251b99..9b59d975e 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.cpp +++ b/Telegram/SourceFiles/data/data_emoji_statuses.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_user.h" #include "data/data_session.h" +#include "data/data_star_gift.h" #include "data/data_document.h" #include "data/data_wall_paper.h" #include "data/stickers/data_stickers.h" @@ -537,4 +538,25 @@ void EmojiStatuses::set( } } +EmojiStatusId EmojiStatuses::fromUniqueGift( + const Data::UniqueGift &gift) { + const auto collectibleId = gift.id; + auto &collectible = _collectibleData[collectibleId]; + if (!collectible) { + collectible = std::make_shared( + EmojiStatusCollectible{ + .id = gift.id, + .documentId = gift.model.document->id, + .title = Data::UniqueGiftName(gift), + .slug = gift.slug, + .patternDocumentId = gift.pattern.document->id, + .centerColor = gift.backdrop.centerColor, + .edgeColor = gift.backdrop.edgeColor, + .patternColor = gift.backdrop.patternColor, + .textColor = gift.backdrop.textColor, + }); + } + return { .collectible = collectible }; +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.h b/Telegram/SourceFiles/data/data_emoji_statuses.h index 167b908a0..70ce4903d 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.h +++ b/Telegram/SourceFiles/data/data_emoji_statuses.h @@ -21,6 +21,7 @@ namespace Data { class DocumentMedia; class Session; +struct UniqueGift; struct EmojiStatusCollectible { CollectibleId id = 0; @@ -79,6 +80,7 @@ public: void set(EmojiStatusId id, TimeId until = 0); void set(not_null peer, EmojiStatusId id, TimeId until = 0); + [[nodiscard]] EmojiStatusId fromUniqueGift(const Data::UniqueGift &gift); void registerAutomaticClear(not_null peer, TimeId until); diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h index 1dea80468..8b79881c4 100644 --- a/Telegram/SourceFiles/data/data_star_gift.h +++ b/Telegram/SourceFiles/data/data_star_gift.h @@ -37,6 +37,7 @@ struct UniqueGiftOriginalDetails { }; struct UniqueGift { + CollectibleId id = 0; QString slug; QString title; QString ownerName; diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 7d49abc92..f2d222be4 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_emoji_statuses.h" #include "data/data_file_origin.h" #include "data/data_photo_media.h" #include "data/data_session.h" @@ -861,6 +862,24 @@ void FillUniqueGiftMenu( } }, st.transfer ? st.transfer : &st::menuIconReplace); } + const auto wear = e.in + && (unique->ownerId == show->session().userPeerId()); + if (wear) { + const auto peer = show->session().user(); + const auto name = UniqueGiftName(*unique); + const auto now = peer->emojiStatusId().collectible; + if (now && unique->slug == now->slug) { + menu->addAction(tr::lng_gift_transfer_take_off(tr::now), [=] { + show->session().data().emojiStatuses().set(peer, {}); + show->showToast( + tr::lng_gift_wear_end_toast(tr::now, lt_name, name)); + }, st.transfer ? st.transfer : &st::menuIconReplace); + } else { + menu->addAction(tr::lng_gift_transfer_wear(tr::now), [=] { + ShowUniqueGiftWearBox(show, *unique, st); + }, st.transfer ? st.transfer : &st::menuIconReplace); + } + } } CreditsEntryBoxStyleOverrides DarkCreditsEntryBoxStyle() { diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 2fde78ee2..b5bafb659 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -188,6 +188,8 @@ uniqueGiftSubtitle: FlatLabel(defaultFlatLabel) { align: align(top); } uniqueGiftSubtitleTop: 170px; +uniqueGiftUserpicTop: 48px; +uniqueGiftUserpicSize: 80px; uniqueGiftBottom: 20px; uniqueCloseButton: IconButton(boxTitleClose) { icon: icon {{ "box_button_close", videoPlayIconFg }};