Implement unique gift view box.

This commit is contained in:
John Preston 2024-12-27 17:04:23 +04:00
parent 522ca3b04a
commit 42c350243a
9 changed files with 347 additions and 85 deletions

View file

@ -3254,11 +3254,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_unique_model" = "Model";
"lng_gift_unique_backdrop" = "Backdrop";
"lng_gift_unique_symbol" = "Symbol";
"lng_gift_unique_rarity" = "Only {percent} of such collectibles have this attribute.";
"lng_gift_unique_availability#one" = "{count} of {amount} issued";
"lng_gift_unique_availability#other" = "{count} of {amount} issued";
"lng_gift_unique_info" = "Gifted to {recipient} on {date}.";
"lng_gift_unique_info_sender" = "Gifted by {from} to {recipient} on {date}.";
"lng_gift_unique_info_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\".";
"lng_gift_unique_info_sender_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\".";
"lng_gift_unique_info_reciever" = "Gifted to {recipient} on {date}.";
"lng_gift_unique_info_reciever_comment" = "Gifted to {recipient} on {date} with the comment \"{text}\".";
"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";

View file

@ -906,8 +906,10 @@ Data::UniqueGiftOriginalDetails FromTL(
const MTPDstarGiftAttributeOriginalDetails &data) {
auto result = Data::UniqueGiftOriginalDetails();
result.date = data.vdate().v;
result.senderId = peerFromUser(
UserId(data.vsender_id().value_or_empty()));
result.senderId = data.vsender_id()
? peerFromUser(
UserId(data.vsender_id().value_or_empty()))
: PeerId();
result.recipientId = peerFromUser(
UserId(data.vrecipient_id().v));
result.message = data.vmessage()

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_premium.h"
#include "api/api_premium_option.h"
#include "apiwrap.h"
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "base/weak_ptr.h"
#include "boxes/peer_list_controllers.h" // ContactsBoxController.
@ -44,12 +45,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/widgets/label_with_custom_emoji.h"
#include "ui/widgets/tooltip.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/table_layout.h"
@ -65,6 +68,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kRarityTooltipDuration = 3 * crl::time(1000);
[[nodiscard]] QString CreateMessageLink(
not_null<Main::Session*> session,
PeerId peerId,
@ -237,6 +242,57 @@ void AddTableRow(
valueMargins);
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeAttributeValue(
not_null<Ui::RpWidget*> parent,
const Data::UniqueGiftAttribute &attribute,
Fn<void(not_null<Ui::RpWidget*>, int)> showTooltip) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
attribute.name,
st::giveawayGiftCodeValue);
const auto permille = attribute.rarityPermille;
const auto text = QString::number(permille / 10.) + '%';
const auto rarity = Ui::CreateChild<Ui::RoundButton>(
raw,
rpl::single(text),
st::starGiftSmallButton);
rarity->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
rpl::combine(
raw->widthValue(),
rarity->widthValue()
) | rpl::start_with_next([=](int width, int convertWidth) {
const auto convertSkip = convertWidth
? (st::normalFont->spacew + convertWidth)
: 0;
label->resizeToNaturalWidth(width - convertSkip);
label->moveToLeft(0, 0, width);
rarity->moveToLeft(
label->width() + st::normalFont->spacew,
(st::giveawayGiftCodeValue.style.font->ascent
- st::starGiftSmallButton.style.font->ascent),
width);
}, label->lifetime());
label->heightValue() | rpl::start_with_next([=](int height) {
raw->resize(
raw->width(),
height + st::giveawayGiftCodeValueMargin.bottom());
}, raw->lifetime());
label->setAttribute(Qt::WA_TransparentForMouseEvents);
rarity->setClickedCallback([=] {
showTooltip(rarity, permille);
});
return result;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
@ -1146,7 +1202,15 @@ void AddStarGiftTable(
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
const auto session = &controller->session();
if (peerId) {
const auto unique = entry.uniqueGift.get();
if (unique) {
AddTableRow(
table,
tr::lng_gift_unique_owner(),
MakePeerTableValue(table, controller, peerId, false),
st::giveawayGiftCodePeerMargin);
//"lng_gift_unique_owner_change" = "change";
} else if (peerId) {
const auto user = session->data().peer(peerId)->asUser();
const auto withSendButton = entry.in && user && !user->isBot();
AddTableRow(
@ -1161,21 +1225,21 @@ void AddStarGiftTable(
MakeHiddenPeerTableValue(table, controller),
st::giveawayGiftCodePeerMargin);
}
if (!entry.firstSaleDate.isNull()) {
if (!unique && !entry.firstSaleDate.isNull()) {
AddTableRow(
table,
tr::lng_gift_link_label_first_sale(),
rpl::single(Ui::Text::WithEntities(
langDateTime(entry.firstSaleDate))));
}
if (!entry.lastSaleDate.isNull()) {
if (!unique && !entry.lastSaleDate.isNull()) {
AddTableRow(
table,
tr::lng_gift_link_label_last_sale(),
rpl::single(Ui::Text::WithEntities(
langDateTime(entry.lastSaleDate))));
}
if (!entry.date.isNull()) {
if (!unique && !entry.date.isNull()) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
@ -1183,7 +1247,87 @@ void AddStarGiftTable(
}
const auto marginWithButton = st::giveawayGiftCodeValueMargin
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
{
if (unique) {
const auto raw = std::make_shared<Ui::ImportantTooltip*>(nullptr);
const auto showTooltip = [=](
not_null<Ui::RpWidget*> widget,
int rarity) {
if (*raw) {
(*raw)->toggleAnimated(false);
}
const auto text = QString::number(rarity / 10.) + '%';
const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
container,
Ui::MakeNiceTooltipLabel(
container,
tr::lng_gift_unique_rarity(
lt_percent,
rpl::single(TextWithEntities{ text }),
Ui::Text::WithEntities),
st::boxWideWidth,
st::defaultImportantTooltipLabel),
st::defaultImportantTooltip);
tooltip->toggleFast(false);
const auto update = [=] {
const auto geometry = Ui::MapFrom(
container,
widget,
widget->rect());
const auto countPosition = [=](QSize size) {
const auto left = geometry.x()
+ (geometry.width() - size.width()) / 2;
const auto right = container->width()
- st::normalFont->spacew;
return QPoint(
std::max(std::min(left, right - size.width()), 0),
geometry.y() - size.height() - st::normalFont->descent);
};
tooltip->pointAt(geometry, RectPart::Top, countPosition);
};
container->widthValue(
) | rpl::start_with_next(update, tooltip->lifetime());
update();
tooltip->toggleAnimated(true);
*raw = tooltip;
tooltip->shownValue() | rpl::filter(
!rpl::mappers::_1
) | rpl::start_with_next([=] {
crl::on_main(tooltip, [=] {
if (tooltip->isHidden()) {
if (*raw == tooltip) {
*raw = nullptr;
}
delete tooltip;
}
});
}, tooltip->lifetime());
base::timer_once(
kRarityTooltipDuration
) | rpl::start_with_next([=] {
tooltip->toggleAnimated(false);
}, tooltip->lifetime());
};
AddTableRow(
table,
tr::lng_gift_unique_model(),
MakeAttributeValue(container, unique->model, showTooltip),
marginWithButton);
AddTableRow(
table,
tr::lng_gift_unique_backdrop(),
MakeAttributeValue(container, unique->backdrop, showTooltip),
marginWithButton);
AddTableRow(
table,
tr::lng_gift_unique_symbol(),
MakeAttributeValue(container, unique->pattern, showTooltip),
marginWithButton);
} else {
AddTableRow(
table,
tr::lng_gift_link_label_value(),
@ -1212,19 +1356,21 @@ void AddStarGiftTable(
AddTableRow(
table,
tr::lng_gift_availability(),
((entry.limitedLeft > 0)
? tr::lng_gift_availability_left(
lt_count_decimal,
rpl::single(entry.limitedLeft * 1.),
((!unique && !entry.limitedLeft)
? tr::lng_gift_availability_none(
lt_amount,
std::move(amount),
Ui::Text::WithEntities)
: tr::lng_gift_availability_none(
lt_amount,
std::move(amount),
Ui::Text::WithEntities)));
: (unique
? tr::lng_gift_unique_availability
: tr::lng_gift_availability_left)(
lt_count_decimal,
rpl::single(entry.limitedLeft * 1.),
lt_amount,
std::move(amount),
Ui::Text::WithEntities)));
}
if (!entry.uniqueGift && startUpgrade) {
if (!unique && startUpgrade) {
AddTableRow(
table,
tr::lng_gift_unique_status(),
@ -1234,7 +1380,72 @@ void AddStarGiftTable(
std::move(startUpgrade)),
marginWithButton);
}
if (!entry.description.empty()) {
if (unique) {
const auto &original = unique->originalDetails;
if (original.recipientId) {
const auto owner = &controller->session().data();
const auto to = owner->peer(original.recipientId);
const auto from = original.senderId
? owner->peer(original.senderId).get()
: nullptr;
const auto date = base::unixtime::parse(original.date).date();
const auto dateText = TextWithEntities{ langDayOfMonth(date) };
auto label = object_ptr<Ui::FlatLabel>(
table,
(from
? (original.message.empty()
? tr::lng_gift_unique_info_sender(
lt_from,
rpl::single(Ui::Text::Link(from->name(), 2)),
lt_recipient,
rpl::single(Ui::Text::Link(to->name(), 1)),
lt_date,
rpl::single(dateText),
Ui::Text::WithEntities)
: tr::lng_gift_unique_info_sender_comment(
lt_from,
rpl::single(Ui::Text::Link(from->name(), 2)),
lt_recipient,
rpl::single(Ui::Text::Link(to->name(), 1)),
lt_date,
rpl::single(dateText),
lt_text,
rpl::single(original.message),
Ui::Text::WithEntities))
: (original.message.empty()
? tr::lng_gift_unique_info_reciever(
lt_recipient,
rpl::single(Ui::Text::Link(to->name(), 1)),
lt_date,
rpl::single(dateText),
Ui::Text::WithEntities)
: tr::lng_gift_unique_info_reciever_comment(
lt_recipient,
rpl::single(Ui::Text::Link(to->name(), 1)),
lt_date,
rpl::single(dateText),
lt_text,
rpl::single(original.message),
Ui::Text::WithEntities))),
st::giveawayGiftMessage);
const auto showBoxLink = [=](not_null<PeerData*> peer) {
return std::make_shared<LambdaClickHandler>([=] {
controller->uiShow()->showBox(
PrepareShortInfoBox(peer, controller));
});
};
label->setLink(1, showBoxLink(to));
if (from) {
label->setLink(2, showBoxLink(from));
}
label->setSelectable(true);
table->addRow(
std::move(label),
nullptr,
st::giveawayGiftCodeLabelMargin,
st::giveawayGiftCodeValueMargin);
}
} else if (!entry.description.empty()) {
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = session,

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "api/api_premium.h"
#include "boxes/gift_premium_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/send_credits_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
@ -1718,7 +1719,9 @@ void AddUniqueGiftCover(
const auto title = CreateChild<FlatLabel>(
cover,
tr::lng_gift_upgrade_title(tr::now),
rpl::duplicate(
data
) | rpl::map([](const Data::UniqueGift &now) { return now.title; }),
st::uniqueGiftTitle);
title->setTextColorOverride(QColor(255, 255, 255));
auto subtitleText = subtitleOverride
@ -2072,6 +2075,8 @@ void UpgradeBox(
button->moveToLeft(padding.left(), padding.top());
}
}, box->lifetime());
AddUniqueCloseButton(box);
}
void PaintPoints(
@ -2155,4 +2160,19 @@ void ShowStarGiftUpgradeBox(
}).send();
}
void AddUniqueCloseButton(not_null<GenericBox*> box) {
const auto button = Ui::CreateChild<IconButton>(
box,
st::uniqueCloseButton);
button->show();
button->raise();
box->widthValue() | rpl::start_with_next([=](int width) {
button->moveToRight(0, 0, width);
button->raise();
}, button->lifetime());
button->setClickedCallback([=] {
box->closeBox();
});
}
} // namespace Ui

View file

@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
struct UniqueGift;
struct GiftCode;
struct CreditsHistoryEntry;
} // namespace Data
namespace Window {
@ -21,6 +23,7 @@ class CustomEmoji;
namespace Ui {
class GenericBox;
class VerticalLayout;
void ChooseStarGiftRecipient(
@ -51,4 +54,6 @@ void ShowStarGiftUpgradeBox(
int stars,
Fn<void(bool)> ready);
void AddUniqueCloseButton(not_null<GenericBox*> box);
} // namespace Ui

View file

@ -2380,7 +2380,7 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
message,
HistoryView::GenerateUniqueGiftMedia(message, replacing, raw),
HistoryView::MediaGenericDescriptor{
.maxWidth = st::chatIntroWidth,
.maxWidth = st::msgServiceGiftBoxSize.width(),
.paintBg = HistoryView::UniqueGiftBg(message, raw),
.service = true,
});

View file

@ -846,6 +846,7 @@ void ReceiptCreditsBox(
+ controller->session().appConfig().stargiftConvertPeriodMax();
const auto timeLeft = int64(convertLast) - int64(base::unixtime::now());
const auto timeExceeded = (timeLeft <= 0);
const auto uniqueGift = e.uniqueGift.get();
const auto forConvert = gotStarGift
&& e.starsConverted
&& !e.converted
@ -859,9 +860,11 @@ void ReceiptCreditsBox(
box->setNoContentMargin(true);
const auto content = box->verticalLayout();
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
if (!uniqueGift) {
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
}
using Type = Data::CreditsHistoryEntry::PeerType;
@ -882,7 +885,15 @@ void ReceiptCreditsBox(
: e.barePeerId
? session->data().peer(PeerId(e.barePeerId)).get()
: nullptr;
if (const auto callback = Ui::PaintPreviewCallback(session, e)) {
if (uniqueGift) {
box->setNoContentMargin(true);
AddUniqueGiftCover(content, rpl::single(*uniqueGift));
AddSkip(content, st::defaultVerticalListSkip * 2);
AddUniqueCloseButton(box);
} else if (const auto callback = Ui::PaintPreviewCallback(session, e)) {
const auto thumb = content->add(object_ptr<Ui::CenterWrap<>>(
content,
GenericEntryPhoto(content, callback, stUser.photoSize)));
@ -994,45 +1005,46 @@ void ReceiptCreditsBox(
}, widget->lifetime());
}
Ui::AddSkip(content);
Ui::AddSkip(content);
if (!uniqueGift) {
Ui::AddSkip(content);
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
object_ptr<Ui::FlatLabel>(
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
rpl::single(!s.title.isEmpty()
? s.title
: !s.until.isNull()
? tr::lng_credits_box_subscription_title(tr::now)
: isPrize
? tr::lng_credits_box_history_entry_giveaway_name(tr::now)
: (!e.subscriptionUntil.isNull() && e.title.isEmpty())
? tr::lng_credits_box_history_entry_subscription(tr::now)
: !e.title.isEmpty()
? e.title
: e.starrefCommission
? tr::lng_credits_commission(
tr::now,
lt_amount,
Info::BotStarRef::FormatCommission(e.starrefCommission))
: e.soldOutInfo
? tr::lng_credits_box_history_entry_gift_unavailable(tr::now)
: sentStarGift
? tr::lng_credits_box_history_entry_gift_sent(tr::now)
: convertedStarGift
? tr::lng_credits_box_history_entry_gift_converted(tr::now)
: (isStarGift && !gotStarGift)
? tr::lng_gift_link_label_gift(tr::now)
: e.gift
? tr::lng_credits_box_history_entry_gift_name(tr::now)
: (peer && !e.reaction)
? peer->name()
: Ui::GenerateEntryName(e).text),
st::creditsBoxAboutTitle)));
Ui::AddSkip(content);
object_ptr<Ui::FlatLabel>(
box,
rpl::single(!s.title.isEmpty()
? s.title
: !s.until.isNull()
? tr::lng_credits_box_subscription_title(tr::now)
: isPrize
? tr::lng_credits_box_history_entry_giveaway_name(tr::now)
: (!e.subscriptionUntil.isNull() && e.title.isEmpty())
? tr::lng_credits_box_history_entry_subscription(tr::now)
: !e.title.isEmpty()
? e.title
: e.starrefCommission
? tr::lng_credits_commission(
tr::now,
lt_amount,
Info::BotStarRef::FormatCommission(e.starrefCommission))
: e.soldOutInfo
? tr::lng_credits_box_history_entry_gift_unavailable(tr::now)
: sentStarGift
? tr::lng_credits_box_history_entry_gift_sent(tr::now)
: convertedStarGift
? tr::lng_credits_box_history_entry_gift_converted(tr::now)
: (isStarGift && !gotStarGift)
? tr::lng_gift_link_label_gift(tr::now)
: e.gift
? tr::lng_credits_box_history_entry_gift_name(tr::now)
: (peer && !e.reaction)
? peer->name()
: Ui::GenerateEntryName(e).text),
st::creditsBoxAboutTitle)));
Ui::AddSkip(content);
}
if (!isStarGift || creditsHistoryStarGift || e.soldOutInfo) {
constexpr auto kMinus = QChar(0x2212);
auto &lifetime = content->lifetime();
@ -1163,7 +1175,7 @@ void ReceiptCreditsBox(
rpl::single(e.description),
st::creditsBoxAbout)));
}
if (gotStarGift) {
if (!uniqueGift && gotStarGift) {
Ui::AddSkip(content);
const auto about = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
@ -1630,32 +1642,34 @@ void StarGiftViewBox(
not_null<Window::SessionController*> controller,
const Data::GiftCode &data,
not_null<HistoryItem*> item) {
const auto entry = Data::CreditsHistoryEntry{
.id = data.slug,
.description = data.message,
.date = base::unixtime::parse(item->date()),
.credits = StarsAmount(data.count),
.bareMsgId = uint64(item->id.bare),
.barePeerId = item->history()->peer->id.value,
.bareGiftStickerId = data.document ? data.document->id : 0,
.stargiftId = data.stargiftId,
.uniqueGift = data.unique,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
.limitedCount = data.limitedCount,
.limitedLeft = data.limitedLeft,
.starsConverted = data.starsConverted,
.starsToUpgrade = data.starsToUpgrade,
.starsUpgradedBySender = data.starsUpgradedBySender,
.converted = data.converted,
.anonymous = data.anonymous,
.stargift = true,
.savedToProfile = data.saved,
.canUpgradeGift = data.upgradable,
.in = true,
.gift = true,
};
Settings::ReceiptCreditsBox(
box,
controller,
Data::CreditsHistoryEntry{
.id = data.slug,
.description = data.message,
.date = base::unixtime::parse(item->date()),
.credits = StarsAmount(data.count),
.bareMsgId = uint64(item->id.bare),
.barePeerId = item->history()->peer->id.value,
.bareGiftStickerId = data.document ? data.document->id : 0,
.stargiftId = data.stargiftId,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
.limitedCount = data.limitedCount,
.limitedLeft = data.limitedLeft,
.starsConverted = data.starsConverted,
.starsToUpgrade = data.starsToUpgrade,
.starsUpgradedBySender = data.starsUpgradedBySender,
.converted = data.converted,
.anonymous = data.anonymous,
.stargift = true,
.savedToProfile = data.saved,
.canUpgradeGift = data.upgradable,
.in = true,
.gift = true,
},
entry,
Data::SubscriptionEntry());
}

View file

@ -185,3 +185,10 @@ uniqueGiftSubtitle: FlatLabel(defaultFlatLabel) {
}
uniqueGiftSubtitleTop: 170px;
uniqueGiftBottom: 20px;
uniqueCloseButton: IconButton(boxTitleClose) {
icon: icon {{ "box_button_close", videoPlayIconFg }};
iconOver: icon {{ "box_button_close", videoPlayIconFg }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: shadowFg;
}
}

@ -1 +1 @@
Subproject commit b063197b5d05de96754894630e45c8b3f95ade62
Subproject commit daf6a614276954bf69c3322ef04985c02560fdf4