Support giveaway message layout.

This commit is contained in:
John Preston 2023-10-06 11:15:26 +04:00
parent d5147c9d28
commit b08869abdb
14 changed files with 659 additions and 251 deletions

View file

@ -2091,7 +2091,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_participants_new#one" = "All users who joined the channel below after this date:";
"lng_prizes_participants_new#other" = "All users who joined the channels below after this date:";
"lng_prizes_date" = "Winners Selection Date";
"lng_prizes_how_works" = "How does it work?";
"lng_prizes_how_works" = "Learn more";
"lng_prizes_how_text#one" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscription for {duration} for its followers.";
"lng_prizes_how_text#other" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions for {duration} for its followers.";
"lng_prizes_how_when_all_of_one#one" = "On {date}, Telegram will automatically select {count} random subscribers of {channel}.";

View file

@ -96,6 +96,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
#include "payments/payments_checkout_process.h"
#include "payments/payments_form.h"
#include "base/random.h"
#include <QtGui/QClipboard>
#include <QtWidgets/QApplication>
#include <QtCore/QMimeData>
@ -449,6 +453,43 @@ HistoryInner::HistoryInner(
_migrated->translateTo(_history->translatedTo());
}
#if 0
if (const auto channel = _history->peer->asBroadcast()) {
if (channel->amCreator()) {
const auto weak = base::make_weak(_controller);
channel->session().api().request(MTPpayments_GetPremiumGiftCodeOptions(
MTP_flags(MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer),
channel->input
)).done(crl::guard(weak, [=](const MTPVector<MTPPremiumGiftCodeOption> &result) {
if (result.v.isEmpty()) {
return;
}
const auto &data = result.v.front().data();
const auto randomId = base::RandomValue<uint64>();
Payments::CheckoutProcess::Start(
Payments::InvoicePremiumGiftCode{
.purpose = Payments::InvoicePremiumGiftCodeGiveaway{
.boostPeer = channel,
//.additionalChannels = ,
.untilDate = (base::unixtime::now() + 300),
.onlyNewSubscribers = true,
},
.randomId = randomId,
.currency = qs(data.vcurrency()),
.amount = data.vamount().v,
.storeProduct = qs(data.vstore_product().value_or_empty()),
.storeQuantity = data.vstore_quantity().value_or_empty(),
.users = data.vusers().v,
.months = data.vmonths().v,
},
crl::guard(weak, [=](auto) { weak->window().activate(); }));
})).fail(crl::guard(weak, [=](const MTP::Error &error) {
weak.get()->showToast(error.type());
})).send();
}
}
#endif
Window::ChatThemeValueFromPeer(
controller,
_peer

View file

@ -338,7 +338,6 @@ void UnreadBar::paint(
text);
}
void DateBadge::init(const QString &date) {
text = date;
width = st::msgServiceFont->width(text);
@ -361,6 +360,81 @@ void DateBadge::paint(
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
}
void ServicePreMessage::init(TextWithEntities string) {
text = Ui::Text::String(
st::serviceTextStyle,
string,
kMarkupTextOptions,
st::msgMinWidth);
}
int ServicePreMessage::resizeToWidth(int newWidth, bool chatWide) {
width = newWidth;
if (chatWide) {
accumulate_min(
width,
st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
}
auto contentWidth = width;
contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins
if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) {
contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1;
}
auto maxWidth = text.maxWidth()
+ st::msgServicePadding.left()
+ st::msgServicePadding.right();
auto minHeight = text.minHeight();
auto nwidth = qMax(contentWidth
- st::msgServicePadding.left()
- st::msgServicePadding.right(), 0);
height = (contentWidth >= maxWidth)
? minHeight
: text.countHeight(nwidth);
height += st::msgServicePadding.top()
+ st::msgServicePadding.bottom()
+ st::msgServiceMargin.top()
+ st::msgServiceMargin.bottom();
return height;
}
void ServicePreMessage::paint(
Painter &p,
const PaintContext &context,
QRect g,
bool chatWide) const {
const auto top = g.top() - height - st::msgMargin.top();
p.translate(0, top);
const auto rect = QRect(0, 0, width, height)
- st::msgServiceMargin;
const auto trect = rect - st::msgServicePadding;
ServiceMessagePainter::PaintComplexBubble(
p,
context.st,
rect.left(),
rect.width(),
text,
trect);
p.setBrush(Qt::NoBrush);
p.setPen(context.st->msgServiceFg());
p.setFont(st::msgServiceFont);
text.draw(p, {
.position = trect.topLeft(),
.availableWidth = trect.width(),
.align = style::al_top,
.palette = &context.st->serviceTextPalette(),
.now = context.now,
//.selection = context.selection,
.fullWidthSelection = false,
});
p.translate(0, -top);
}
void FakeBotAboutTop::init() {
if (!text.isEmpty()) {
return;
@ -971,7 +1045,9 @@ bool Element::computeIsAttachToPrevious(not_null<Element*> previous) {
|| !item->from()->isChannel());
};
const auto item = data();
if (!Has<DateBadge>() && !Has<UnreadBar>()) {
if (!Has<DateBadge>()
&& !Has<UnreadBar>()
&& !Has<ServicePreMessage>()) {
const auto prev = previous->data();
const auto previousMarkup = prev->inlineReplyMarkup();
const auto possible = (std::abs(prev->date() - item->date())
@ -1183,6 +1259,18 @@ void Element::setDisplayDate(bool displayDate) {
}
}
void Element::setServicePreMessage(TextWithEntities text) {
if (!text.empty()) {
AddComponents(ServicePreMessage::Bit());
const auto service = Get<ServicePreMessage>();
service->init(std::move(text));
setPendingResize();
} else if (Has<ServicePreMessage>()) {
RemoveComponents(ServicePreMessage::Bit());
setPendingResize();
}
}
void Element::setAttachToNext(bool attachToNext, Element *next) {
Expects(next || !attachToNext);

View file

@ -234,6 +234,26 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
};
// Any HistoryView::Element can have this Component for
// displaying some text in layout of a service message above the message.
struct ServicePreMessage
: public RuntimeComponent<ServicePreMessage, Element> {
void init(TextWithEntities string);
int resizeToWidth(int newWidth, bool chatWide);
void paint(
Painter &p,
const PaintContext &context,
QRect g,
bool chatWide) const;
Ui::Text::String text;
int width = 0;
int height = 0;
};
struct FakeBotAboutTop : public RuntimeComponent<FakeBotAboutTop, Element> {
void init();
@ -339,6 +359,7 @@ public:
// For blocks context this should be called only from recountDisplayDate().
void setDisplayDate(bool displayDate);
void setServicePreMessage(TextWithEntities text);
bool computeIsAttachToPrevious(not_null<Element*> previous);

View file

@ -874,6 +874,9 @@ int Message::marginTop() const {
if (const auto bar = Get<UnreadBar>()) {
result += bar->height();
}
if (const auto service = Get<ServicePreMessage>()) {
result += service->height;
}
return result;
}
@ -911,6 +914,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
}
}
if (const auto service = Get<ServicePreMessage>()) {
service->paint(p, context, g, delegate()->elementIsChatWide());
}
if (isHidden()) {
return;
}
@ -3043,6 +3050,9 @@ bool Message::hasFromName() const {
if (hasOutLayout() && !item->from()->isChannel()) {
return false;
} else if (!peer->isUser()) {
if (const auto media = this->media()) {
return !media->hideFromName();
}
return true;
}
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
@ -3703,6 +3713,10 @@ int Message::resizeContentGetHeight(int newWidth) {
auto newHeight = minHeight();
if (const auto service = Get<ServicePreMessage>()) {
service->resizeToWidth(newWidth, delegate()->elementIsChatWide());
}
const auto item = data();
const auto botTop = item->isFakeBotAbout()
? Get<FakeBotAboutTop>()

View file

@ -83,12 +83,23 @@ inline auto WebPageToPhrase(not_null<WebPageData*> webpage) {
: QString());
}
[[nodiscard]] ClickHandlerPtr MakeWebPageButtonClickHandler(
[[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler(
not_null<Data::Media*> media) {
Expects(media->webpage() != nullptr);
if (const auto giveaway = media->giveaway()) {
return std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
if (!controller) {
return;
}
});
}
const auto webpage = media->webpage();
Assert(webpage != nullptr);
const auto url = media->webpage()->url;
const auto type = media->webpage()->type;
const auto url = webpage->url;
const auto type = webpage->type;
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
@ -105,6 +116,15 @@ inline auto WebPageToPhrase(not_null<WebPageData*> webpage) {
});
}
[[nodiscard]] QString MakeMediaButtonText(not_null<Data::Media*> media) {
if (const auto giveaway = media->giveaway()) {
return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now));
}
const auto webpage = media->webpage();
Assert(webpage != nullptr);
return WebPageToPhrase(webpage);
}
[[nodiscard]] ClickHandlerPtr SponsoredLink(
not_null<HistoryMessageSponsored*> sponsored) {
if (!sponsored->externalLink.isEmpty()) {
@ -170,7 +190,7 @@ struct ViewButton::Inner {
bool ViewButton::MediaHasViewButton(not_null<Data::Media*> media) {
return media->webpage()
? MediaHasViewButton(media->webpage())
: false;
: (media->giveaway() != nullptr);
}
bool ViewButton::MediaHasViewButton(
@ -209,10 +229,10 @@ ViewButton::Inner::Inner(
not_null<Data::Media*> media,
Fn<void()> updateCallback)
: margins(st::historyViewButtonMargins)
, link(MakeWebPageButtonClickHandler(media))
, link(MakeMediaButtonClickHandler(media))
, updateCallback(std::move(updateCallback))
, belowInfo(false)
, text(st::historyViewButtonTextStyle, WebPageToPhrase(media->webpage())) {
, text(st::historyViewButtonTextStyle, MakeMediaButtonText(media)) {
}
void ViewButton::Inner::updateMask(int height) {

View file

@ -8,74 +8,91 @@ 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 "lang/lang_keys.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/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 {
void TextRows::add(Ui::Text::String text, int skipTop) {
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 };
}
bool TextRows::isEmpty() const {
return _rows.empty()
|| (_rows.size() == 1 && _rows.front().text.isEmpty());
}
int TextRows::maxWidth() const {
return 0;
}
int TextRows::minHeight() const {
return 0;
}
int TextRows::countHeight(int newWidth) const {
return 0;
}
int TextRows::length() const {
return 0;
}
TextSelection UnshiftItemSelection(
TextSelection selection,
const TextRows &byText) {
return UnshiftItemSelection(selection, byText.length());
}
TextSelection ShiftItemSelection(
TextSelection selection,
const TextRows &byText) {
return ShiftItemSelection(selection, byText.length());
}
} // namespace
Giveaway::Giveaway(
not_null<Element*> parent,
not_null<Data::Giveaway*> giveaway)
: Media(parent) {
: 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() = default;
Giveaway::~Giveaway() {
if (hasHeavyPart()) {
unloadHeavyPart();
_parent->checkHeavyPart();
}
}
void Giveaway::fillFromData(not_null<Data::Giveaway*> giveaway) {
_rows.add(Ui::Text::String(
_months = giveaway->months;
_prizesTitle.setText(
st::semiboldTextStyle,
tr::lng_prizes_title(tr::now, lt_count, giveaway->quantity),
kDefaultTextOptions,
st::msgMinWidth), st::chatGiveawayPrizesTop);
kDefaultTextOptions);
const auto duration = (giveaway->months < 12)
? tr::lng_months(tr::now, lt_count, giveaway->months)
: tr::lng_years(tr::now, lt_count, giveaway->months / 12);
_rows.add(Ui::Text::String(
_prizes.setMarkedText(
st::defaultTextStyle,
tr::lng_prizes_about(
tr::now,
@ -84,94 +101,123 @@ void Giveaway::fillFromData(not_null<Data::Giveaway*> giveaway) {
lt_duration,
Ui::Text::Bold(duration),
Ui::Text::RichLangValue),
kDefaultTextOptions,
st::msgMinWidth), st::chatGiveawayPrizesSkip);
_rows.add(Ui::Text::String(
kDefaultTextOptions);
_participantsTitle.setText(
st::semiboldTextStyle,
tr::lng_prizes_participants(tr::now),
kDefaultTextOptions,
st::msgMinWidth), st::chatGiveawayParticipantsTop);
kDefaultTextOptions);
for (const auto &channel : giveaway->channels) {
_channels.push_back({ Ui::Text::String(
st::semiboldTextStyle,
channel->name(),
kDefaultTextOptions,
st::msgMinWidth)
_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());
_rows.add(Ui::Text::String(
_participants.setText(
st::defaultTextStyle,
(giveaway->all
? tr::lng_prizes_participants_all
: tr::lng_prizes_participants_new)(tr::now, lt_count, channels),
kDefaultTextOptions,
st::msgMinWidth), st::chatGiveawayParticipantsSkip);
_date.add(Ui::Text::String(
kDefaultTextOptions);
_winnersTitle.setText(
st::semiboldTextStyle,
tr::lng_prizes_date(tr::now),
kDefaultTextOptions,
st::msgMinWidth), st::chatGiveawayDateTop);
_rows.add(Ui::Text::String(
kDefaultTextOptions);
_winners.setText(
st::defaultTextStyle,
langDateTime(base::unixtime::parse(giveaway->untilDate)),
kDefaultTextOptions,
st::msgMinWidth), st::chatGiveawayDateSkip);
kDefaultTextOptions);
ensureStickerCreated();
}
QSize Giveaway::countOptimalSize() {
// init dimensions
auto skipBlockWidth = _parent->skipBlockWidth();
auto maxWidth = skipBlockWidth;
auto minHeight = 0;
const auto maxWidth = st::chatGiveawayWidth;
const auto padding = inBubblePadding();
const auto available = maxWidth - padding.left() - padding.right();
accumulate_max(maxWidth, _rows.maxWidth());
minHeight += _rows.minHeight();
_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 };
}
//minHeight +=
accumulate_max(maxWidth, _date.maxWidth());
minHeight += _date.minHeight();
auto padding = inBubblePadding();
maxWidth += padding.left() + padding.right();
minHeight += padding.top() + padding.bottom();
return { maxWidth, minHeight };
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) {
accumulate_min(newWidth, maxWidth());
auto innerWidth = newWidth
- st::msgPadding.left()
- st::msgPadding.right();
auto newHeight = 0;
_rowsHeight = _rows.countHeight(innerWidth);
newHeight += _rowsHeight;
//newHeight +=
newHeight += _date.countHeight(innerWidth);
_dateHeight = _date.minHeight();
newHeight += _dateHeight;
auto padding = inBubblePadding();
newHeight += padding.top() + padding.bottom();
return { newWidth, newHeight };
}
TextSelection Giveaway::toDateSelection(TextSelection selection) const {
return UnshiftItemSelection(selection, _rows);
}
TextSelection Giveaway::fromDateSelection(TextSelection selection) const {
return ShiftItemSelection(selection, _rows);
return { maxWidth(), minHeight()};
}
void Giveaway::draw(Painter &p, const PaintContext &context) const {
@ -184,24 +230,107 @@ void Giveaway::draw(Painter &p, const PaintContext &context) const {
auto &semibold = stm->msgServiceFg;
auto padding = inBubblePadding();
auto tshift = padding.top();
//_rows.draw(p, {
// .position = { padding.left(), tshift },
// .outerWidth = width(),
// .availableWidth = paintw,
// .now = context.now,
// .selection = context.selection,
//});
//tshift += _rows.countHeight(paintw);
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());
//_date.draw(p, {
// .position = { padding.left(), tshift },
// .outerWidth = width(),
// .availableWidth = paintw,
// .now = context.now,
// .selection = toDateSelection(context.selection),
//});
if (_sticker) {
_sticker->draw(p, context, sticker);
} 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::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();
}
}
}
TextState Giveaway::textState(QPoint point, StateRequest request) const {
@ -211,80 +340,29 @@ TextState Giveaway::textState(QPoint point, StateRequest request) const {
return result;
}
auto padding = inBubblePadding();
auto tshift = padding.top();
auto bshift = padding.bottom();
auto symbolAdd = 0;
if (_rowsHeight > 0) {
if (point.y() >= tshift && point.y() < tshift + _rowsHeight) {
//result = TextState(_parent, _rows.getState(
// point - QPoint(padding.left(), tshift),
// paintw,
// width(),
// request.forText()));
} else if (point.y() >= tshift + _rowsHeight) {
symbolAdd += _rows.length();
for (const auto &channel : _channels) {
if (channel.geometry.contains(point)) {
result.link = channel.link;
return result;
}
tshift += _rowsHeight;
}
if (_channelsHeight > 0) {
tshift += _channelsHeight;
}
if (_dateHeight > 0) {
if (point.y() >= tshift && point.y() < tshift + _dateHeight) {
//result = TextState(_parent, _date.getState(
// point - QPoint(padding.left(), tshift),
// paintw,
// width(),
// request.forText()));
} else if (point.y() >= tshift + _dateHeight) {
symbolAdd += _date.length();
}
tshift += _dateHeight;
}
result.symbol += symbolAdd;
return result;
}
TextSelection Giveaway::adjustSelection(
TextSelection selection,
TextSelectType type) const {
//if (_date.isEmpty() || selection.to <= _rows.length()) {
// return _rows.adjustSelection(selection, type);
//}
//const auto dateSelection = _date.adjustSelection(
// toDateSelection(selection),
// type);
//if (selection.from >= _rows.length()) {
// return fromDateSelection(dateSelection);
//}
//const auto rowsSelection = _rows.adjustSelection(selection, type);
//return { rowsSelection.from, fromDateSelection(dateSelection).to };
return selection;
bool Giveaway::hideFromName() const {
return !parent()->data()->Has<HistoryMessageForwarded>();
}
bool Giveaway::hasHeavyPart() const {
return false;
return _subscribedToThumbnails;
}
void Giveaway::unloadHeavyPart() {
}
uint16 Giveaway::fullSelectionLength() const {
return 0;
}
TextForMimeData Giveaway::selectedText(TextSelection selection) const {
//auto rowsResult = _rows.toTextForMimeData(selection);
//auto dateResult = _date.toTextForMimeData(toDateSelection(selection));
//if (rowsResult.empty()) {
// return dateResult;
//} else if (dateResult.empty()) {
// return rowsResult;
//}
//return rowsResult.append('\n').append(std::move(dateResult));
return {};
if (base::take(_subscribedToThumbnails)) {
for (const auto &channel : _channels) {
channel.thumbnail->subscribeToUpdates(nullptr);
}
}
}
QMargins Giveaway::inBubblePadding() const {

View file

@ -8,40 +8,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "history/view/media/history_view_media.h"
#include "history/view/media/history_view_sticker.h"
namespace Data {
struct Giveaway;
} // namespace Data
namespace Dialogs::Stories {
class Thumbnail;
} // namespace Dialogs::Stories
namespace HistoryView {
class TextRows final {
public:
void add(Ui::Text::String text, int skipTop);
[[nodiscard]] bool isEmpty() const;
[[nodiscard]] int maxWidth() const;
[[nodiscard]] int minHeight() const;
[[nodiscard]] int countHeight(int newWidth) const;
[[nodiscard]] int length() const;
private:
struct Row {
Ui::Text::String text;
int skipTop = 0;
};
std::vector<Row> _rows;
};
[[nodiscard]] TextSelection UnshiftItemSelection(
TextSelection selection,
const TextRows &byText);
[[nodiscard]] TextSelection ShiftItemSelection(
TextSelection selection,
const TextRows &byText);
class Giveaway final : public Media {
public:
Giveaway(
@ -67,35 +45,54 @@ public:
return true;
}
[[nodiscard]] TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const override;
uint16 fullSelectionLength() const override;
TextForMimeData selectedText(TextSelection selection) const override;
bool hideFromName() const override;
void unloadHeavyPart() override;
bool hasHeavyPart() const override;
private:
using Thumbnail = Dialogs::Stories::Thumbnail;
struct Channel {
Ui::Text::String name;
std::shared_ptr<Thumbnail> thumbnail;
QRect geometry;
ClickHandlerPtr link;
};
void paintChannels(Painter &p, const PaintContext &context) const;
int layoutChannels(int x, int y, int available);
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
void fillFromData(not_null<Data::Giveaway*> giveaway);
void ensureStickerCreated() const;
TextSelection toDateSelection(TextSelection selection) const;
TextSelection fromDateSelection(TextSelection selection) const;
QMargins inBubblePadding() const;
[[nodiscard]] QMargins inBubblePadding() const;
TextRows _rows;
mutable std::optional<Sticker> _sticker;
Ui::Text::String _prizesTitle;
Ui::Text::String _prizes;
Ui::Text::String _participantsTitle;
Ui::Text::String _participants;
std::vector<Channel> _channels;
TextRows _date;
int _rowsHeight = 0;
int _channelsHeight = 0;
int _dateHeight = 0;
Ui::Text::String _winnersTitle;
Ui::Text::String _winners;
mutable QColor _channelBg;
mutable std::array<QImage, 4> _channelCorners;
int _months = 0;
int _stickerTop = 0;
int _prizesTitleTop = 0;
int _prizesTop = 0;
int _prizesWidth = 0;
int _participantsTitleTop = 0;
int _participantsTop = 0;
int _participantsWidth = 0;
int _winnersTitleTop = 0;
int _winnersTop = 0;
mutable bool _subscribedToThumbnails = false;
};

View file

@ -101,6 +101,9 @@ public:
[[nodiscard]] virtual bool hideServiceText() const {
return false;
}
[[nodiscard]] virtual bool hideFromName() const {
return false;
}
[[nodiscard]] virtual bool allowsFastShare() const {
return false;
}

View file

@ -38,6 +38,7 @@ namespace {
struct SessionProcesses {
base::flat_map<FullMsgId, std::unique_ptr<CheckoutProcess>> byItem;
base::flat_map<QString, std::unique_ptr<CheckoutProcess>> bySlug;
base::flat_map<uint64, std::unique_ptr<CheckoutProcess>> byRandomId;
base::flat_map<FullMsgId, PaidInvoice> paymentStartedByItem;
base::flat_map<QString, PaidInvoice> paymentStartedBySlug;
rpl::lifetime lifetime;
@ -118,6 +119,28 @@ void CheckoutProcess::Start(
j->second->requestActivate();
}
void CheckoutProcess::Start(
InvoicePremiumGiftCode giftCodeInvoice,
Fn<void(CheckoutResult)> reactivate) {
const auto randomId = giftCodeInvoice.randomId;
auto id = InvoiceId{ std::move(giftCodeInvoice) };
auto &processes = LookupSessionProcesses(SessionFromId(id));
const auto i = processes.byRandomId.find(randomId);
if (i != end(processes.byRandomId)) {
i->second->setReactivateCallback(std::move(reactivate));
i->second->requestActivate();
return;
}
const auto j = processes.byRandomId.emplace(
randomId,
std::make_unique<CheckoutProcess>(
std::move(id),
Mode::Payment,
std::move(reactivate),
PrivateTag{})).first;
j->second->requestActivate();
}
std::optional<PaidInvoice> CheckoutProcess::InvoicePaid(
not_null<const HistoryItem*> item) {
const auto session = &item->history()->session();
@ -139,7 +162,8 @@ std::optional<PaidInvoice> CheckoutProcess::InvoicePaid(
} else if (i->second.paymentStartedByItem.empty()
&& i->second.byItem.empty()
&& i->second.paymentStartedBySlug.empty()
&& i->second.bySlug.empty()) {
&& i->second.bySlug.empty()
&& i->second.byRandomId.empty()) {
Processes.erase(i);
}
return result;
@ -165,7 +189,8 @@ std::optional<PaidInvoice> CheckoutProcess::InvoicePaid(
} else if (i->second.paymentStartedByItem.empty()
&& i->second.byItem.empty()
&& i->second.paymentStartedBySlug.empty()
&& i->second.bySlug.empty()) {
&& i->second.bySlug.empty()
&& i->second.byRandomId.empty()) {
Processes.erase(i);
}
return result;
@ -192,6 +217,11 @@ void CheckoutProcess::RegisterPaymentStart(
return;
}
}
for (const auto &[randomId, itemProcess] : i->second.byRandomId) {
if (itemProcess.get() == process) {
return;
}
}
}
void CheckoutProcess::UnregisterPaymentStart(
@ -212,10 +242,16 @@ void CheckoutProcess::UnregisterPaymentStart(
break;
}
}
for (const auto &[randomId, itemProcess] : i->second.byRandomId) {
if (itemProcess.get() == process) {
break;
}
}
if (i->second.paymentStartedByItem.empty()
&& i->second.byItem.empty()
&& i->second.paymentStartedBySlug.empty()
&& i->second.bySlug.empty()) {
&& i->second.bySlug.empty()
&& i->second.byRandomId.empty()) {
Processes.erase(i);
}
}
@ -497,8 +533,16 @@ void CheckoutProcess::close() {
if (k != end(entry.bySlug)) {
entry.bySlug.erase(k);
}
const auto l = ranges::find(
entry.byRandomId,
this,
[](const auto &pair) { return pair.second.get(); });
if (l != end(entry.byRandomId)) {
entry.byRandomId.erase(l);
}
if (entry.byItem.empty()
&& entry.bySlug.empty()
&& i->second.byRandomId.empty()
&& entry.paymentStartedByItem.empty()
&& entry.paymentStartedBySlug.empty()) {
Processes.erase(i);

View file

@ -37,6 +37,7 @@ class Form;
struct FormUpdate;
struct Error;
struct InvoiceId;
struct InvoicePremiumGiftCode;
enum class Mode {
Payment,
@ -68,6 +69,9 @@ public:
not_null<Main::Session*> session,
const QString &slug,
Fn<void(CheckoutResult)> reactivate);
static void Start(
InvoicePremiumGiftCode giftCodeInvoice,
Fn<void(CheckoutResult)> reactivate);
[[nodiscard]] static std::optional<PaidInvoice> InvoicePaid(
not_null<const HistoryItem*> item);
[[nodiscard]] static std::optional<PaidInvoice> InvoicePaid(

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_form.h"
#include "main/main_session.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_media_types.h"
#include "data/data_user.h"
@ -112,10 +113,21 @@ constexpr auto kPasswordPeriod = 15 * TimeId(60);
} // namespace
not_null<Main::Session*> SessionFromId(const InvoiceId &id) {
if (const auto slug = std::get_if<InvoiceSlug>(&id.value)) {
if (const auto message = std::get_if<InvoiceMessage>(&id.value)) {
return &message->peer->session();
} else if (const auto slug = std::get_if<InvoiceSlug>(&id.value)) {
return slug->session;
}
return &v::get<InvoiceMessage>(id.value).peer->session();
const auto &giftCode = v::get<InvoicePremiumGiftCode>(id.value);
const auto users = std::get_if<InvoicePremiumGiftCodeUsers>(
&giftCode.purpose);
if (users) {
Assert(!users->users.empty());
return &users->users.front()->session();
}
const auto &giveaway = v::get<InvoicePremiumGiftCodeGiveaway>(
giftCode.purpose);
return &giveaway.boostPeer->session();
}
Form::Form(InvoiceId id, bool receipt)
@ -207,11 +219,10 @@ void Form::loadThumbnail(not_null<PhotoData*> photo) {
}
Data::FileOrigin Form::thumbnailFileOrigin() const {
if (const auto slug = std::get_if<InvoiceSlug>(&_id.value)) {
return Data::FileOrigin();
if (const auto message = std::get_if<InvoiceMessage>(&_id.value)) {
return FullMsgId(message->peer->id, message->itemId);
}
const auto message = v::get<InvoiceMessage>(_id.value);
return FullMsgId(message.peer->id, message.itemId);
return Data::FileOrigin();
}
QImage Form::prepareGoodThumbnail(
@ -257,13 +268,67 @@ QImage Form::prepareEmptyThumbnail() const {
}
MTPInputInvoice Form::inputInvoice() const {
if (const auto slug = std::get_if<InvoiceSlug>(&_id.value)) {
if (const auto message = std::get_if<InvoiceMessage>(&_id.value)) {
return MTP_inputInvoiceMessage(
message->peer->input,
MTP_int(message->itemId.bare));
} else if (const auto slug = std::get_if<InvoiceSlug>(&_id.value)) {
return MTP_inputInvoiceSlug(MTP_string(slug->slug));
}
const auto message = v::get<InvoiceMessage>(_id.value);
return MTP_inputInvoiceMessage(
message.peer->input,
MTP_int(message.itemId.bare));
const auto &giftCode = v::get<InvoicePremiumGiftCode>(_id.value);
using Flag = MTPDpremiumGiftCodeOption::Flag;
const auto option = MTP_premiumGiftCodeOption(
MTP_flags((giftCode.storeQuantity ? Flag::f_store_quantity : Flag())
| (giftCode.storeProduct.isEmpty()
? Flag()
: Flag::f_store_product)),
MTP_int(giftCode.users),
MTP_int(giftCode.months),
MTP_string(giftCode.storeProduct),
MTP_int(giftCode.storeQuantity),
MTP_string(giftCode.currency),
MTP_long(giftCode.amount));
const auto users = std::get_if<InvoicePremiumGiftCodeUsers>(
&giftCode.purpose);
if (users) {
using Flag = MTPDinputStorePaymentPremiumGiftCode::Flag;
return MTP_inputInvoicePremiumGiftCode(
MTP_inputStorePaymentPremiumGiftCode(
MTP_flags(users->boostPeer ? Flag::f_boost_peer : Flag()),
MTP_vector<MTPInputUser>(ranges::views::all(
users->users
) | ranges::views::transform([](not_null<UserData*> user) {
return MTPInputUser(user->inputUser);
}) | ranges::to<QVector>),
users->boostPeer ? users->boostPeer->input : MTPInputPeer(),
MTP_string(giftCode.currency),
MTP_long(giftCode.amount)),
option);
} else {
const auto &giveaway = v::get<InvoicePremiumGiftCodeGiveaway>(
giftCode.purpose);
using Flag = MTPDinputStorePaymentPremiumGiveaway::Flag;
return MTP_inputInvoicePremiumGiftCode(
MTP_inputStorePaymentPremiumGiveaway(
MTP_flags(Flag()
| (giveaway.onlyNewSubscribers
? Flag::f_only_new_subscribers
: Flag())
| (giveaway.additionalChannels.empty()
? Flag()
: Flag::f_additional_peers)),
giveaway.boostPeer->input,
MTP_vector<MTPInputPeer>(ranges::views::all(
giveaway.additionalChannels
) | ranges::views::transform([](not_null<ChannelData*> c) {
return MTPInputPeer(c->input);
}) | ranges::to<QVector>()),
MTP_long(giftCode.randomId),
MTP_int(giveaway.untilDate),
MTP_string(giftCode.currency),
MTP_long(giftCode.amount)),
option);
}
}
void Form::requestForm() {

View file

@ -186,8 +186,34 @@ struct InvoiceSlug {
QString slug;
};
struct InvoicePremiumGiftCodeGiveaway {
not_null<ChannelData*> boostPeer;
std::vector<not_null<ChannelData*>> additionalChannels;
TimeId untilDate = 0;
bool onlyNewSubscribers = false;
};
struct InvoicePremiumGiftCodeUsers {
std::vector<not_null<UserData*>> users;
ChannelData *boostPeer = nullptr;
};
struct InvoicePremiumGiftCode {
std::variant<
InvoicePremiumGiftCodeUsers,
InvoicePremiumGiftCodeGiveaway> purpose;
uint64 randomId = 0;
QString currency;
uint64 amount = 0;
QString storeProduct;
int storeQuantity = 0;
int users = 0;
int months = 0;
};
struct InvoiceId {
std::variant<InvoiceMessage, InvoiceSlug> value;
std::variant<InvoiceMessage, InvoiceSlug, InvoicePremiumGiftCode> value;
};
[[nodiscard]] not_null<Main::Session*> SessionFromId(const InvoiceId &id);

View file

@ -932,9 +932,16 @@ storyMentionReadSkipTwice: 7px;
storyMentionReadStrokeTwice: 3px;
storyMentionButtonSkip: 5px;
chatGiveawayPrizesTop: 4px;
chatGiveawayWidth: 292px;
chatGiveawayStickerTop: -16px;
chatGiveawayPrizesTop: 16px;
chatGiveawayPrizesSkip: 4px;
chatGiveawayParticipantsTop: 16px;
chatGiveawayParticipantsSkip: 4px;
chatGiveawayDateTop: 16px;
chatGiveawayChannelTop: 6px;
chatGiveawayChannelSize: 32px;
chatGiveawayChannelPadding: margins(5px, 7px, 12px, 0px);
chatGiveawayChannelSkip: 8px;
chatGiveawayDateTop: 6px;
chatGiveawayDateSkip: 4px;
chatGiveawayBottomSkip: 16px;