tdesktop/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp
2023-10-26 14:08:40 +04:00

458 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 "history/view/media/history_view_giveaway.h"
#include "base/unixtime.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_media_types.h"
#include "data/data_session.h"
#include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_stories_list.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/message_bubble.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/tooltip.h"
#include "ui/painter.h"
#include "ui/round_rect.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
constexpr auto kChannelBgAlpha = 32;
[[nodiscard]] QSize CountOptimalTextSize(
const Ui::Text::String &text,
int minWidth,
int maxWidth) {
if (text.maxWidth() <= maxWidth) {
return { text.maxWidth(), text.minHeight() };
}
const auto height = text.countHeight(maxWidth);
return { Ui::FindNiceTooltipWidth(minWidth, maxWidth, [&](int width) {
return text.countHeight(width);
}), height };
}
} // namespace
Giveaway::Giveaway(
not_null<Element*> parent,
not_null<Data::Giveaway*> giveaway)
: Media(parent)
, _prizesTitle(st::msgMinWidth)
, _prizes(st::msgMinWidth)
, _participantsTitle(st::msgMinWidth)
, _participants(st::msgMinWidth)
, _winnersTitle(st::msgMinWidth)
, _winners(st::msgMinWidth) {
fillFromData(giveaway);
if (!parent->data()->Has<HistoryMessageForwarded>()
&& ranges::contains(
giveaway->channels,
parent->data()->history()->peer)) {
parent->setServicePreMessage({
tr::lng_action_giveaway_started(
tr::now,
lt_from,
parent->data()->history()->peer->name()),
});
}
}
Giveaway::~Giveaway() {
if (hasHeavyPart()) {
unloadHeavyPart();
_parent->checkHeavyPart();
}
}
void Giveaway::fillFromData(not_null<Data::Giveaway*> giveaway) {
_months = giveaway->months;
_quantity = giveaway->quantity;
_prizesTitle.setText(
st::semiboldTextStyle,
tr::lng_prizes_title(tr::now, lt_count, _quantity),
kDefaultTextOptions);
const auto duration = (_months < 12)
? tr::lng_months(tr::now, lt_count, _months)
: tr::lng_years(tr::now, lt_count, _months / 12);
_prizes.setMarkedText(
st::defaultTextStyle,
tr::lng_prizes_about(
tr::now,
lt_count,
_quantity,
lt_duration,
Ui::Text::Bold(duration),
Ui::Text::RichLangValue),
kDefaultTextOptions);
_participantsTitle.setText(
st::semiboldTextStyle,
tr::lng_prizes_participants(tr::now),
kDefaultTextOptions);
for (const auto &channel : giveaway->channels) {
_channels.push_back({
.name = Ui::Text::String(
st::semiboldTextStyle,
channel->name(),
kDefaultTextOptions,
st::msgMinWidth),
.thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel),
.link = channel->openLink(),
});
}
const auto channels = int(_channels.size());
_participants.setText(
st::defaultTextStyle,
(giveaway->all
? tr::lng_prizes_participants_all
: tr::lng_prizes_participants_new)(tr::now, lt_count, channels),
kDefaultTextOptions);
_winnersTitle.setText(
st::semiboldTextStyle,
tr::lng_prizes_date(tr::now),
kDefaultTextOptions);
_winners.setText(
st::defaultTextStyle,
langDateTime(base::unixtime::parse(giveaway->untilDate)),
kDefaultTextOptions);
ensureStickerCreated();
}
QSize Giveaway::countOptimalSize() {
const auto maxWidth = st::chatGiveawayWidth;
const auto padding = inBubblePadding();
const auto available = maxWidth - padding.left() - padding.right();
_stickerTop = st::chatGiveawayStickerTop;
_prizesTitleTop = _stickerTop
+ st::msgServiceGiftBoxStickerSize.height()
+ st::chatGiveawayPrizesTop;
_prizesTop = _prizesTitleTop
+ _prizesTitle.countHeight(available)
+ st::chatGiveawayPrizesSkip;
const auto prizesSize = CountOptimalTextSize(
_prizes,
st::msgMinWidth,
available);
_prizesWidth = prizesSize.width();
_participantsTitleTop = _prizesTop
+ prizesSize.height()
+ st::chatGiveawayParticipantsTop;
_participantsTop = _participantsTitleTop
+ _participantsTitle.countHeight(available)
+ st::chatGiveawayParticipantsSkip;
const auto participantsSize = CountOptimalTextSize(
_participants,
st::msgMinWidth,
available);
_participantsWidth = participantsSize.width();
const auto channelsTop = _participantsTop
+ participantsSize.height()
+ st::chatGiveawayChannelTop;
const auto channelsBottom = layoutChannels(
padding.left(),
channelsTop,
available);
_winnersTitleTop = channelsBottom + st::chatGiveawayDateTop;
_winnersTop = _winnersTitleTop
+ _winnersTitle.countHeight(available)
+ st::chatGiveawayDateSkip;
const auto height = _winnersTop
+ _winners.countHeight(available)
+ st::chatGiveawayBottomSkip;
return { maxWidth, height };
}
int Giveaway::layoutChannels(int x, int y, int available) {
const auto size = st::chatGiveawayChannelSize;
const auto skip = st::chatGiveawayChannelSkip;
const auto padding = st::chatGiveawayChannelPadding;
auto left = available;
const auto shiftRow = [&](int i, int top, int shift) {
for (auto j = i; j != 0; --j) {
auto &geometry = _channels[j - 1].geometry;
if (geometry.top() != top) {
break;
}
geometry.moveLeft(geometry.x() + shift);
}
};
const auto count = int(_channels.size());
for (auto i = 0; i != count; ++i) {
const auto desired = size
+ padding.left()
+ _channels[i].name.maxWidth()
+ padding.right();
const auto width = std::min(desired, available);
if (left < width) {
shiftRow(i, y, (left + skip) / 2);
left = available;
y += size + skip;
}
_channels[i].geometry = { x + available - left, y, width, size };
left -= width + skip;
}
shiftRow(count, y, (left + skip) / 2);
return y + size + skip;
}
QSize Giveaway::countCurrentSize(int newWidth) {
return { maxWidth(), minHeight()};
}
void Giveaway::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
const auto st = context.st;
const auto sti = context.imageStyle();
const auto stm = context.messageStyle();
auto &semibold = stm->msgServiceFg;
auto padding = inBubblePadding();
const auto outer = width();
const auto paintw = outer - padding.left() - padding.right();
const auto stickerSize = st::msgServiceGiftBoxStickerSize;
const auto sticker = QRect(
(outer - stickerSize.width()) / 2,
_stickerTop,
stickerSize.width(),
stickerSize.height());
if (_sticker) {
_sticker->draw(p, context, sticker);
paintBadge(p, context);
} else {
ensureStickerCreated();
}
const auto paintText = [&](
const Ui::Text::String &text,
int top,
int width) {
p.setPen(stm->historyTextFg);
text.draw(p, {
.position = { padding.left() + (paintw - width) / 2, top},
.outerWidth = outer,
.availableWidth = width,
.align = style::al_top,
.palette = &stm->textPalette,
.now = context.now,
});
};
paintText(_prizesTitle, _prizesTitleTop, paintw);
paintText(_prizes, _prizesTop, _prizesWidth);
paintText(_participantsTitle, _participantsTitleTop, paintw);
paintText(_participants, _participantsTop, _participantsWidth);
paintText(_winnersTitle, _winnersTitleTop, paintw);
paintText(_winners, _winnersTop, paintw);
paintChannels(p, context);
}
void Giveaway::paintBadge(Painter &p, const PaintContext &context) const {
validateBadge(context);
const auto badge = _badge.size() / _badge.devicePixelRatio();
const auto left = (width() - badge.width()) / 2;
const auto top = st::chatGiveawayBadgeTop;
const auto rect = QRect(left, top, badge.width(), badge.height());
const auto paintContent = [&](QPainter &q) {
q.drawImage(rect.topLeft(), _badge);
};
{
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(context.messageStyle()->msgFileBg);
const auto half = st::chatGiveawayBadgeStroke / 2.;
const auto inner = QRectF(rect).marginsRemoved(
{ half, half, half, half });
const auto radius = inner.height() / 2.;
p.drawRoundedRect(inner, radius, radius);
}
if (!usesBubblePattern(context)) {
paintContent(p);
} else {
Ui::PaintPatternBubblePart(
p,
context.viewport,
context.bubblesPattern->pixmap,
rect,
paintContent,
_badgeCache);
}
}
void Giveaway::paintChannels(
Painter &p,
const PaintContext &context) const {
if (_channels.empty()) {
return;
}
const auto size = _channels[0].geometry.height();
const auto ratio = style::DevicePixelRatio();
const auto stm = context.messageStyle();
auto bg = stm->msgReplyBarColor->c;
bg.setAlpha(kChannelBgAlpha);
if (_channelCorners[0].isNull() || _channelBg != bg) {
_channelBg = bg;
_channelCorners = Images::CornersMask(size / 2);
for (auto &image : _channelCorners) {
style::colorizeImage(image, bg, &image);
}
}
p.setPen(stm->msgReplyBarColor);
const auto padding = st::chatGiveawayChannelPadding;
for (const auto &channel : _channels) {
const auto &thumbnail = channel.thumbnail;
const auto &geometry = channel.geometry;
if (!_subscribedToThumbnails) {
thumbnail->subscribeToUpdates([view = parent()] {
view->history()->owner().requestViewRepaint(view);
});
}
Ui::DrawRoundedRect(p, geometry, _channelBg, _channelCorners);
p.drawImage(geometry.topLeft(), thumbnail->image(size));
const auto left = size + padding.left();
const auto top = padding.top();
const auto available = geometry.width() - left - padding.right();
channel.name.draw(p, {
.position = { geometry.left() + left, geometry.top() + top },
.outerWidth = width(),
.availableWidth = available,
.align = style::al_left,
.palette = &stm->textPalette,
.now = context.now,
.elisionOneLine = true,
.elisionBreakEverywhere = true,
});
}
_subscribedToThumbnails = true;
}
void Giveaway::ensureStickerCreated() const {
if (_sticker) {
return;
}
const auto &session = _parent->history()->session();
auto &packs = session.giftBoxStickersPacks();
if (const auto document = packs.lookup(_months)) {
if (const auto sticker = document->sticker()) {
const auto skipPremiumEffect = false;
_sticker.emplace(_parent, document, skipPremiumEffect, _parent);
_sticker->setDiceIndex(sticker->alt, 1);
_sticker->setGiftBoxSticker(true);
_sticker->initSize();
}
}
}
void Giveaway::validateBadge(const PaintContext &context) const {
const auto stm = context.messageStyle();
const auto &badgeFg = stm->historyFileRadialFg->c;
const auto &badgeBorder = stm->msgBg->c;
if (!_badge.isNull()
&& _badgeFg == badgeFg
&& _badgeBorder == badgeBorder) {
return;
}
const auto &font = st::chatGiveawayBadgeFont;
_badgeFg = badgeFg;
_badgeBorder = badgeBorder;
const auto text = tr::lng_prizes_badge(
tr::now,
lt_amount,
QString::number(_quantity));
const auto width = font->width(text);
const auto inner = QRect(0, 0, width, font->height);
const auto rect = inner.marginsAdded(st::chatGiveawayBadgePadding);
const auto size = rect.size();
const auto ratio = style::DevicePixelRatio();
_badge = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
_badge.setDevicePixelRatio(ratio);
_badge.fill(Qt::transparent);
auto p = QPainter(&_badge);
auto hq = PainterHighQualityEnabler(p);
p.setPen(QPen(_badgeBorder, st::chatGiveawayBadgeStroke * 1.));
p.setBrush(Qt::NoBrush);
const auto half = st::chatGiveawayBadgeStroke / 2.;
const auto smaller = QRectF(
rect.translated(-rect.topLeft())
).marginsRemoved({ half, half, half, half });
const auto radius = smaller.height() / 2.;
p.drawRoundedRect(smaller, radius, radius);
p.setPen(_badgeFg);
p.setFont(font);
p.drawText(
st::chatGiveawayBadgePadding.left(),
st::chatGiveawayBadgePadding.top() + font->ascent,
text);
}
TextState Giveaway::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result;
}
for (const auto &channel : _channels) {
if (channel.geometry.contains(point)) {
result.link = channel.link;
return result;
}
}
return result;
}
bool Giveaway::hideFromName() const {
return !parent()->data()->Has<HistoryMessageForwarded>();
}
bool Giveaway::hasHeavyPart() const {
return _subscribedToThumbnails;
}
void Giveaway::unloadHeavyPart() {
if (base::take(_subscribedToThumbnails)) {
for (const auto &channel : _channels) {
channel.thumbnail->subscribeToUpdates(nullptr);
}
}
}
QMargins Giveaway::inBubblePadding() const {
auto lshift = st::msgPadding.left();
auto rshift = st::msgPadding.right();
auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip;
auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip;
return QMargins(lshift, tshift, rshift, bshift);
}
} // namespace HistoryView