Allow wearing unique gifts as status.

This commit is contained in:
John Preston 2025-01-09 18:38:40 +04:00
parent fecddb5203
commit 232077b919
9 changed files with 243 additions and 0 deletions

View file

@ -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.";

View file

@ -805,6 +805,7 @@ std::optional<Data::StarGift> FromTL(
auto result = Data::StarGift{
.id = uint64(data.vid().v),
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
.id = data.vid().v,
.slug = qs(data.vslug()),
.title = qs(data.vtitle()),
.ownerName = qs(data.vowner_name().value_or_empty()),

View file

@ -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<VerticalLayout*> container,
const Data::UniqueGift &data,
not_null<PeerData*> peer) {
const auto cover = container->add(object_ptr<RpWidget>(container));
const auto title = CreateChild<FlatLabel>(
cover,
rpl::single(peer->name()),
st::uniqueGiftTitle);
title->setTextColorOverride(QColor(255, 255, 255));
const auto subtitle = CreateChild<FlatLabel>(
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<Text::CustomEmoji> emoji;
base::flat_map<float64, QImage> emojis;
rpl::lifetime lifetime;
};
const auto state = cover->lifetime().make_state<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<ChatHelpers::Show> show,
const Data::UniqueGift &gift,
Settings::CreditsEntryBoxStyleOverrides st) {
show->show(Box([=](not_null<Ui::GenericBox*> 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<QString> title,
rpl::producer<QString> text,
not_null<const style::icon*> icon) {
auto raw = content->add(
object_ptr<Ui::VerticalLayout>(content));
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(title) | Ui::Text::ToBold(),
st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding);
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(text),
st::boxDividerLabel),
st::settingsPremiumRowAboutPadding);
object_ptr<Info::Profile::FloatingIcon>(
raw,
*icon,
st::starrefInfoIconPosition);
};
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_gift_wear_title(
lt_name,
rpl::single(UniqueGiftName(gift))),
st::uniqueGiftTitle),
st::settingsPremiumRowTitlePadding);
content->add(
object_ptr<Ui::FlatLabel>(
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<Data::UniqueGiftModel> models;
std::vector<Data::UniqueGiftPattern> patterns;

View file

@ -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<VerticalLayout*> container,
rpl::producer<Data::UniqueGift> data,
rpl::producer<QString> subtitleOverride = nullptr);
void AddWearGiftCover(
not_null<VerticalLayout*> container,
const Data::UniqueGift &data,
not_null<PeerData*> peer);
void ShowUniqueGiftWearBox(
std::shared_ptr<ChatHelpers::Show> show,
const Data::UniqueGift &gift,
Settings::CreditsEntryBoxStyleOverrides st);
struct PatternPoint {
QPointF position;

View file

@ -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>(
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

View file

@ -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<PeerData*> peer, EmojiStatusId id, TimeId until = 0);
[[nodiscard]] EmojiStatusId fromUniqueGift(const Data::UniqueGift &gift);
void registerAutomaticClear(not_null<PeerData*> peer, TimeId until);

View file

@ -37,6 +37,7 @@ struct UniqueGiftOriginalDetails {
};
struct UniqueGift {
CollectibleId id = 0;
QString slug;
QString title;
QString ownerName;

View file

@ -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() {

View file

@ -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 }};