mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-07-23 14:03:02 +02:00
470 lines
13 KiB
C++
470 lines
13 KiB
C++
/*
|
|
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 "info/peer_gifts/info_peer_gifts_common.h"
|
|
|
|
#include "boxes/sticker_set_box.h"
|
|
#include "chat_helpers/stickers_gift_box_pack.h"
|
|
#include "chat_helpers/stickers_lottie.h"
|
|
#include "core/ui_integration.h"
|
|
#include "data/stickers/data_custom_emoji.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_document_media.h"
|
|
#include "data/data_session.h"
|
|
#include "history/view/media/history_view_sticker_player.h"
|
|
#include "lang/lang_keys.h"
|
|
#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/effects/premium_graphics.h"
|
|
#include "ui/painter.h"
|
|
#include "window/window_session_controller.h"
|
|
#include "styles/style_credits.h"
|
|
#include "styles/style_layers.h"
|
|
#include "styles/style_premium.h"
|
|
|
|
namespace Info::PeerGifts {
|
|
namespace {
|
|
|
|
constexpr auto kGiftsPerRow = 3;
|
|
|
|
} // namespace
|
|
|
|
GiftButton::GiftButton(
|
|
QWidget *parent,
|
|
not_null<GiftButtonDelegate*> delegate)
|
|
: AbstractButton(parent)
|
|
, _delegate(delegate)
|
|
, _stars(this, true, Ui::Premium::MiniStars::Type::SlowStars) {
|
|
}
|
|
|
|
GiftButton::~GiftButton() {
|
|
unsubscribe();
|
|
}
|
|
|
|
void GiftButton::unsubscribe() {
|
|
if (base::take(_subscribed)) {
|
|
_userpic->subscribeToUpdates(nullptr);
|
|
}
|
|
}
|
|
|
|
void GiftButton::setDescriptor(const GiftDescriptor &descriptor) {
|
|
if (_descriptor == descriptor) {
|
|
return;
|
|
}
|
|
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;
|
|
_text = Ui::Text::String(st::giftBoxGiftHeight / 4);
|
|
_text.setMarkedText(
|
|
st::defaultTextStyle,
|
|
Ui::Text::Bold(years
|
|
? tr::lng_years(tr::now, lt_count, years)
|
|
: tr::lng_months(tr::now, lt_count, months)
|
|
).append('\n').append(
|
|
tr::lng_gift_premium_label(tr::now)
|
|
));
|
|
_price.setText(
|
|
st::semiboldTextStyle,
|
|
Ui::FillAmountAndCurrency(
|
|
data.cost,
|
|
data.currency,
|
|
true));
|
|
_userpic = nullptr;
|
|
_stars.setColorOverride(QGradientStops{
|
|
{ 0., anim::with_alpha(st::windowActiveTextFg->c, .3) },
|
|
{ 1., st::windowActiveTextFg->c },
|
|
});
|
|
}, [&](const GiftTypeStars &data) {
|
|
const auto soldOut = data.info.limitedCount
|
|
&& !data.userpic
|
|
&& !data.info.limitedLeft;
|
|
_price.setMarkedText(
|
|
st::semiboldTextStyle,
|
|
_delegate->star().append(' ' + QString::number(data.info.stars)),
|
|
kMarkupTextOptions,
|
|
_delegate->textContext());
|
|
_userpic = !data.userpic
|
|
? nullptr
|
|
: data.from
|
|
? Ui::MakeUserpicThumbnail(data.from)
|
|
: Ui::MakeHiddenAuthorThumbnail();
|
|
if (soldOut) {
|
|
_stars.setColorOverride(QGradientStops{
|
|
{ 0., Qt::transparent },
|
|
{ 1., Qt::transparent },
|
|
});
|
|
} else {
|
|
_stars.setColorOverride(Ui::Premium::CreditsIconGradientStops());
|
|
}
|
|
});
|
|
if (const auto document = _delegate->lookupSticker(descriptor)) {
|
|
setDocument(document);
|
|
}
|
|
|
|
const auto buttonw = _price.maxWidth();
|
|
const auto buttonh = st::semiboldFont->height;
|
|
const auto inner = QRect(
|
|
QPoint(),
|
|
QSize(buttonw, buttonh)
|
|
).marginsAdded(st::giftBoxButtonPadding);
|
|
const auto skipy = _delegate->buttonSize().height()
|
|
- st::giftBoxButtonBottom
|
|
- inner.height();
|
|
const auto skipx = (width() - inner.width()) / 2;
|
|
const auto outer = (width() - 2 * skipx);
|
|
_button = QRect(skipx, skipy, outer, inner.height());
|
|
{
|
|
const auto padding = _button.height() / 2;
|
|
_stars.setCenter(_button - QMargins(padding, 0, padding, 0));
|
|
}
|
|
}
|
|
|
|
bool GiftButton::documentResolved() const {
|
|
return _player || _mediaLifetime;
|
|
}
|
|
|
|
void GiftButton::setDocument(not_null<DocumentData*> document) {
|
|
const auto media = document->createMediaView();
|
|
media->checkStickerLarge();
|
|
media->goodThumbnailWanted();
|
|
|
|
rpl::single() | rpl::then(
|
|
document->owner().session().downloaderTaskFinished()
|
|
) | rpl::filter([=] {
|
|
return media->loaded();
|
|
}) | rpl::start_with_next([=] {
|
|
_mediaLifetime.destroy();
|
|
|
|
auto result = std::unique_ptr<HistoryView::StickerPlayer>();
|
|
const auto sticker = document->sticker();
|
|
if (sticker->isLottie()) {
|
|
result = std::make_unique<HistoryView::LottiePlayer>(
|
|
ChatHelpers::LottiePlayerFromDocument(
|
|
media.get(),
|
|
ChatHelpers::StickerLottieSize::InlineResults,
|
|
st::giftBoxStickerSize,
|
|
Lottie::Quality::High));
|
|
} else if (sticker->isWebm()) {
|
|
result = std::make_unique<HistoryView::WebmPlayer>(
|
|
media->owner()->location(),
|
|
media->bytes(),
|
|
st::giftBoxStickerSize);
|
|
} else {
|
|
result = std::make_unique<HistoryView::StaticStickerPlayer>(
|
|
media->owner()->location(),
|
|
media->bytes(),
|
|
st::giftBoxStickerSize);
|
|
}
|
|
result->setRepaintCallback([=] { update(); });
|
|
_player = std::move(result);
|
|
update();
|
|
}, _mediaLifetime);
|
|
}
|
|
|
|
void GiftButton::setGeometry(QRect inner, QMargins extend) {
|
|
_extend = extend;
|
|
AbstractButton::setGeometry(inner.marginsAdded(extend));
|
|
}
|
|
|
|
void GiftButton::resizeEvent(QResizeEvent *e) {
|
|
if (!_button.isEmpty()) {
|
|
_button.moveLeft((width() - _button.width()) / 2);
|
|
const auto padding = _button.height() / 2;
|
|
_stars.setCenter(_button - QMargins(padding, 0, padding, 0));
|
|
}
|
|
}
|
|
|
|
void GiftButton::paintEvent(QPaintEvent *e) {
|
|
if (!documentResolved()) {
|
|
if (const auto document = _delegate->lookupSticker(_descriptor)) {
|
|
setDocument(document);
|
|
}
|
|
}
|
|
|
|
auto p = QPainter(this);
|
|
const auto hidden = v::is<GiftTypeStars>(_descriptor)
|
|
&& v::get<GiftTypeStars>(_descriptor).hidden;;
|
|
const auto position = QPoint(_extend.left(), _extend.top());
|
|
const auto background = _delegate->background();
|
|
const auto dpr = int(background.devicePixelRatio());
|
|
const auto width = this->width();
|
|
if (width * dpr <= background.width()) {
|
|
p.drawImage(0, 0, background);
|
|
} else {
|
|
const auto full = background.width();
|
|
const auto half = ((full / 2) / dpr) * dpr;
|
|
const auto height = background.height();
|
|
p.drawImage(
|
|
QRect(0, 0, half / dpr, height / dpr),
|
|
background,
|
|
QRect(0, 0, half, height));
|
|
p.drawImage(
|
|
QRect(width - (half / dpr), 0, half / dpr, height / dpr),
|
|
background,
|
|
QRect(full - half, 0, half, height));
|
|
p.drawImage(
|
|
QRect(half / dpr, 0, width - 2 * (half / dpr), height / dpr),
|
|
background,
|
|
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);
|
|
}
|
|
|
|
auto frame = QImage();
|
|
if (_player && _player->ready()) {
|
|
const auto paused = !isOver();
|
|
auto info = _player->frame(
|
|
st::giftBoxStickerSize,
|
|
QColor(0, 0, 0, 0),
|
|
false,
|
|
crl::now(),
|
|
paused);
|
|
frame = info.image;
|
|
const auto finished = (info.index + 1 == _player->framesCount());
|
|
if (!finished || !paused) {
|
|
_player->markFrameShown();
|
|
}
|
|
const auto size = frame.size() / style::DevicePixelRatio();
|
|
p.drawImage(
|
|
QRect(
|
|
(width - size.width()) / 2,
|
|
(_text.isEmpty()
|
|
? st::giftBoxStickerStarTop
|
|
: st::giftBoxStickerTop),
|
|
size.width(),
|
|
size.height()),
|
|
frame);
|
|
}
|
|
if (hidden) {
|
|
const auto topleft = QPoint(
|
|
(width - st::giftBoxStickerSize.width()) / 2,
|
|
(_text.isEmpty()
|
|
? st::giftBoxStickerStarTop
|
|
: st::giftBoxStickerTop));
|
|
_delegate->hiddenMark()->paint(
|
|
p,
|
|
frame,
|
|
_hiddenBgCache,
|
|
topleft,
|
|
st::giftBoxStickerSize,
|
|
width);
|
|
}
|
|
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
const auto premium = v::is<GiftTypePremium>(_descriptor);
|
|
const auto singlew = width - _extend.left() - _extend.right();
|
|
const auto font = st::semiboldFont;
|
|
p.setFont(font);
|
|
const auto text = v::match(_descriptor, [&](GiftTypePremium data) {
|
|
if (data.discountPercent > 0) {
|
|
p.setBrush(st::attentionButtonFg);
|
|
const auto kMinus = QChar(0x2212);
|
|
return kMinus + QString::number(data.discountPercent) + '%';
|
|
}
|
|
return QString();
|
|
}, [&](const GiftTypeStars &data) {
|
|
if (const auto count = data.info.limitedCount) {
|
|
const auto soldOut = !data.userpic && !data.info.limitedLeft;
|
|
p.setBrush(soldOut
|
|
? st::attentionButtonFg
|
|
: st::windowActiveTextFg);
|
|
return soldOut
|
|
? tr::lng_gift_stars_sold_out(tr::now)
|
|
: !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();
|
|
});
|
|
if (!text.isEmpty()) {
|
|
p.setPen(Qt::NoPen);
|
|
const auto twidth = font->width(text);
|
|
const auto pos = position + QPoint(singlew - twidth, font->height);
|
|
p.save();
|
|
const auto rubberOut = _extend.top();
|
|
const auto inner = rect().marginsRemoved(_extend);
|
|
p.setClipRect(inner.marginsAdded(
|
|
{ rubberOut, rubberOut, rubberOut, rubberOut }));
|
|
p.translate(pos);
|
|
p.rotate(45.);
|
|
p.translate(-pos);
|
|
p.drawRect(-5 * twidth, position.y(), twidth * 12, font->height);
|
|
p.setPen(st::windowBg);
|
|
p.drawText(pos - QPoint(0, font->descent), text);
|
|
p.restore();
|
|
}
|
|
p.setBrush(premium ? st::lightButtonBgOver : st::creditsBg3);
|
|
p.setPen(Qt::NoPen);
|
|
if (!premium) {
|
|
p.setOpacity(0.12);
|
|
}
|
|
const auto geometry = _button;
|
|
const auto radius = geometry.height() / 2.;
|
|
p.drawRoundedRect(geometry, radius, radius);
|
|
if (!premium) {
|
|
p.setOpacity(1.);
|
|
}
|
|
{
|
|
auto clipPath = QPainterPath();
|
|
clipPath.addRoundedRect(geometry, radius, radius);
|
|
p.setClipPath(clipPath);
|
|
_stars.paint(p);
|
|
p.setClipping(false);
|
|
}
|
|
|
|
if (!_text.isEmpty()) {
|
|
p.setPen(st::windowFg);
|
|
_text.draw(p, {
|
|
.position = (position
|
|
+ QPoint(0, st::giftBoxPremiumTextTop)),
|
|
.availableWidth = singlew,
|
|
.align = style::al_top,
|
|
});
|
|
}
|
|
|
|
const auto padding = st::giftBoxButtonPadding;
|
|
p.setPen(premium ? st::windowActiveTextFg : st::creditsFg);
|
|
_price.draw(p, {
|
|
.position = (geometry.topLeft()
|
|
+ QPoint(padding.left(), padding.top())),
|
|
.availableWidth = _price.maxWidth(),
|
|
});
|
|
}
|
|
|
|
Delegate::Delegate(not_null<Window::SessionController*> window)
|
|
: _window(window)
|
|
, _hiddenMark(std::make_unique<StickerPremiumMark>(
|
|
&window->session(),
|
|
st::giftBoxHiddenMark,
|
|
RectPart::Center)) {
|
|
}
|
|
|
|
Delegate::Delegate(Delegate &&other) = default;
|
|
|
|
Delegate::~Delegate() = default;
|
|
|
|
TextWithEntities Delegate::star() {
|
|
const auto owner = &_window->session().data();
|
|
return owner->customEmojiManager().creditsEmoji();
|
|
}
|
|
|
|
std::any Delegate::textContext() {
|
|
return Core::MarkedTextContext{
|
|
.session = &_window->session(),
|
|
.customEmojiRepaint = [] {},
|
|
};
|
|
}
|
|
|
|
QSize Delegate::buttonSize() {
|
|
if (!_single.isEmpty()) {
|
|
return _single;
|
|
}
|
|
const auto width = st::boxWideWidth;
|
|
const auto padding = st::giftBoxPadding;
|
|
const auto available = width - padding.left() - padding.right();
|
|
const auto singlew = (available - 2 * st::giftBoxGiftSkip.x())
|
|
/ kGiftsPerRow;
|
|
_single = QSize(singlew, st::giftBoxGiftHeight);
|
|
return _single;
|
|
}
|
|
|
|
QMargins Delegate::buttonExtend() {
|
|
return st::defaultDropdownMenu.wrap.shadow.extend;
|
|
}
|
|
|
|
QImage Delegate::background() {
|
|
if (!_bg.isNull()) {
|
|
return _bg;
|
|
}
|
|
const auto single = buttonSize();
|
|
const auto extend = buttonExtend();
|
|
const auto bgSize = single.grownBy(extend);
|
|
const auto ratio = style::DevicePixelRatio();
|
|
auto bg = QImage(
|
|
bgSize * ratio,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
bg.setDevicePixelRatio(ratio);
|
|
bg.fill(Qt::transparent);
|
|
|
|
const auto radius = st::giftBoxGiftRadius;
|
|
const auto rect = QRect(QPoint(), bgSize).marginsRemoved(extend);
|
|
|
|
{
|
|
auto p = QPainter(&bg);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
p.setOpacity(0.3);
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(st::windowShadowFg);
|
|
p.drawRoundedRect(
|
|
QRectF(rect).translated(0, radius / 12.),
|
|
radius,
|
|
radius);
|
|
}
|
|
bg = bg.scaled(
|
|
(bgSize * ratio) / 2,
|
|
Qt::IgnoreAspectRatio,
|
|
Qt::SmoothTransformation);
|
|
bg = Images::Blur(std::move(bg), true);
|
|
bg = bg.scaled(
|
|
bgSize * ratio,
|
|
Qt::IgnoreAspectRatio,
|
|
Qt::SmoothTransformation);
|
|
{
|
|
auto p = QPainter(&bg);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(st::windowBg);
|
|
p.drawRoundedRect(rect, radius, radius);
|
|
}
|
|
|
|
_bg = std::move(bg);
|
|
return _bg;
|
|
}
|
|
|
|
DocumentData *Delegate::lookupSticker(const GiftDescriptor &descriptor) {
|
|
return LookupGiftSticker(&_window->session(), descriptor);
|
|
}
|
|
|
|
not_null<StickerPremiumMark*> Delegate::hiddenMark() {
|
|
return _hiddenMark.get();
|
|
}
|
|
|
|
DocumentData *LookupGiftSticker(
|
|
not_null<Main::Session*> session,
|
|
const GiftDescriptor &descriptor) {
|
|
auto &packs = session->giftBoxStickersPacks();
|
|
packs.load();
|
|
return v::match(descriptor, [&](GiftTypePremium data) {
|
|
return packs.lookup(data.months);
|
|
}, [&](GiftTypeStars data) {
|
|
return data.info.document.get();
|
|
});
|
|
}
|
|
|
|
} // namespace Info::PeerGifts
|