Display nice price tag, handle pay in albums.

This commit is contained in:
John Preston 2024-06-19 13:39:02 +04:00
parent a9bd7803e6
commit 5f8da27c86
9 changed files with 288 additions and 46 deletions

View file

@ -4212,6 +4212,67 @@ void ApiWrap::sendMediaWithRandomId(
});
}
void ApiWrap::sendMultiPaidMedia(
not_null<HistoryItem*> item,
const QVector<MTPInputMedia> &medias,
Api::SendOptions options,
uint64 randomId,
Fn<void(bool)> done) {
Expects(options.price > 0);
const auto history = item->history();
const auto replyTo = item->replyTo();
auto caption = item->originalText();
TextUtilities::Trim(caption);
auto sentEntities = Api::EntitiesToMTP(
_session,
caption.entities,
Api::ConvertOption::SkipLocal);
using Flag = MTPmessages_SendMedia::Flag;
const auto flags = Flag(0)
| (replyTo ? Flag::f_reply_to : Flag(0))
| (ShouldSendSilent(history->peer, options)
? Flag::f_silent
: Flag(0))
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
| (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
| (options.effectId ? Flag::f_effect : Flag(0))
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
const auto itemId = item->fullId();
histories.sendPreparedMessage(
history,
replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(flags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
MTP_inputMediaPaidMedia(
MTP_long(options.price),
MTP_vector<MTPInputMedia>(medias)),
MTP_string(caption.text),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(options.scheduled),
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (done) done(true);
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (done) done(false);
sendMessageFail(error, peer, randomId, itemId);
});
}
void ApiWrap::sendAlbumWithUploaded(
not_null<HistoryItem*> item,
const MessageGroupId &groupId,
@ -4266,7 +4327,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
_sendingAlbums.remove(groupId);
return;
} else if (medias.size() < 2) {
const auto &single = medias.front().c_inputSingleMedia();
const auto &single = medias.front().data();
sendMediaWithRandomId(
sample,
single.vmedia(),
@ -4274,6 +4335,19 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
single.vrandom_id().v);
_sendingAlbums.remove(groupId);
return;
} else if (album->options.price > 0) {
const auto &single = medias.front().data();
auto list = medias | ranges::view::transform([](
const MTPInputSingleMedia &media) {
return MTPInputMedia(media.data().vmedia());
}) | ranges::to<QVector<MTPInputMedia>>();
sendMultiPaidMedia(
sample,
std::move(list),
album->options,
single.vrandom_id().v);
_sendingAlbums.remove(groupId);
return;
}
const auto history = sample->history();
const auto replyTo = sample->replyTo();

View file

@ -545,6 +545,12 @@ private:
Api::SendOptions options,
uint64 randomId,
Fn<void(bool)> done = nullptr);
void sendMultiPaidMedia(
not_null<HistoryItem*> item,
const QVector<MTPInputMedia> &medias,
Api::SendOptions options,
uint64 randomId,
Fn<void(bool)> done = nullptr);
void getTopPromotionDelayed(TimeId now, TimeId next);
void topPromotionDone(const MTPhelp_PromoData &proxy);

View file

@ -680,9 +680,11 @@ bool SendFilesBox::hasSpoilerMenu() const {
bool SendFilesBox::canChangePrice() const {
const auto way = _sendWay.current();
return _list.canChangePrice(
way.groupFiles() && way.sendImagesAsPhotos(),
way.sendImagesAsPhotos());
return _captionToPeer
&& _captionToPeer->isBroadcast()
&& _list.canChangePrice(
way.groupFiles() && way.sendImagesAsPhotos(),
way.sendImagesAsPhotos());
}
void SendFilesBox::applyBlockChanges() {
@ -754,6 +756,7 @@ void SendFilesBox::refreshPriceTag() {
raw,
tr::lng_paid_price(lt_price, std::move(price)),
st::paidTagLabel);
label->show();
label->sizeValue() | rpl::start_with_next([=](QSize size) {
const auto inner = QRect(QPoint(), size);
const auto rect = inner.marginsAdded(st::paidTagPadding);
@ -833,6 +836,7 @@ void SendFilesBox::initSendWay() {
block.setSendWay(value);
}
refreshButtons();
refreshPriceTag();
if (was != hidden()) {
updateBoxSize();
updateControlsGeometry();

View file

@ -1186,7 +1186,9 @@ base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
const auto margins = (st.textMargins + st.placeholderMargins);
const auto available = width - margins.left() - margins.right();
label->resizeToWidth(available);
label->moveToLeft(margins.left(), margins.top(), width);
const auto height = label->height() + link->height();
const auto top = (raw->height() - height) / 2;
label->moveToLeft(margins.left(), top, width);
link->move(
(width - link->width()) / 2,
label->y() + label->height());

View file

@ -223,6 +223,20 @@ public:
QPoint point,
StateRequest request) const;
virtual void drawPriceTag(
Painter &p,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const {
Unexpected("Price tag method call.");
}
[[nodiscard]] virtual ClickHandlerPtr priceTagLink() const {
Unexpected("Price tag method call.");
}
[[nodiscard]] virtual QImage priceTagBackground() const {
Unexpected("Price tag method call.");
}
[[nodiscard]] virtual bool animating() const {
return false;
}

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "storage/storage_shared_media.h"
#include "lang/lang_keys.h"
#include "media/streaming/media_streaming_utility.h"
#include "ui/grouped_layout.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/message_bubble.h"
@ -290,6 +291,42 @@ QMargins GroupedMedia::groupedPadding() const {
(normal.bottom() - grouped.bottom()) + addToBottom);
}
Media *GroupedMedia::lookupUnpaidMedia() const {
if (_parts.empty()) {
return nullptr;
}
const auto media = _parts.front().content.get();
const auto photo = media ? media->getPhoto() : nullptr;
return (photo && photo->extendedMediaPreview()) ? media : nullptr;
}
QImage GroupedMedia::generatePriceTagBackground(QRect full) const {
const auto ratio = style::DevicePixelRatio();
auto result = QImage(
full.size() * ratio,
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
auto p = QPainter(&result);
const auto shift = -full.topLeft();
const auto skip1 = st::historyGroupSkip / 2;
const auto skip2 = st::historyGroupSkip - skip1;
for (const auto &part : _parts) {
auto background = part.content->priceTagBackground();
const auto extended = part.geometry.translated(shift).marginsAdded(
{ skip1, skip1, skip2, skip2 });
if (background.isNull()) {
p.fillRect(extended, Qt::black);
} else {
p.drawImage(extended, background);
}
}
p.end();
return ::Media::Streaming::PrepareBlurredBackground(
full.size(),
std::move(result));
}
void GroupedMedia::drawHighlight(
Painter &p,
const PaintContext &context,
@ -351,6 +388,8 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
: adjustedBubbleRounding();
auto highlight = context.highlight.range;
const auto unpaid = lookupUnpaidMedia();
auto fullRect = QRect();
const auto subpartHighlight = IsSubGroupSelection(highlight);
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
const auto &part = _parts[i];
@ -390,11 +429,20 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
if (!part.cache.isNull()) {
nowCache = true;
}
if (unpaid) {
fullRect = fullRect.united(part.geometry);
}
}
if (nowCache && !wasCache) {
history()->owner().registerHeavyViewPart(_parent);
}
if (unpaid) {
unpaid->drawPriceTag(p, fullRect, context, [&] {
return generatePriceTagBackground(fullRect);
});
}
// date
if (_parent->media() == this && (!_parent->hasBubble() || isBubbleBottom())) {
auto fullRight = width();
@ -455,6 +503,11 @@ PointState GroupedMedia::pointState(QPoint point) const {
TextState GroupedMedia::textState(QPoint point, StateRequest request) const {
const auto groupPadding = groupedPadding();
auto result = getPartState(point - QPoint(0, groupPadding.top()), request);
if (const auto unpaid = lookupUnpaidMedia()) {
if (QRect(0, 0, width(), height()).contains(point)) {
result.link = unpaid->priceTagLink();
}
}
if (_parent->media() == this && (!_parent->hasBubble() || isBubbleBottom())) {
auto fullRight = width();
auto fullBottom = height();

View file

@ -149,6 +149,9 @@ private:
RectParts sides) const;
[[nodiscard]] QMargins groupedPadding() const;
[[nodiscard]] Media *lookupUnpaidMedia() const;
[[nodiscard]] QImage generatePriceTagBackground(QRect full) const;
mutable std::optional<HistoryItem*> _captionItem;
std::vector<Part> _parts;
Mode _mode = Mode::Grid;

View file

@ -60,6 +60,14 @@ struct Photo::Streamed {
QImage roundingMask;
};
struct Photo::PriceTag {
uint64 price = 0;
QImage cache;
QColor darken;
QColor fg;
ClickHandlerPtr link;
};
Photo::Streamed::Streamed(
std::shared_ptr<::Media::Streaming::Document> shared)
: instance(std::move(shared), nullptr) {
@ -281,8 +289,8 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
const auto st = context.st;
const auto sti = context.imageStyle();
const auto preview = _data->extendedMediaPreview();
auto loaded = preview || _dataMedia->loaded();
auto displayLoading = !preview && _data->displayLoading();
const auto loaded = preview || _dataMedia->loaded();
const auto displayLoading = !preview && _data->displayLoading();
auto inWebPage = (_parent->media() != this);
auto paintx = 0, painty = 0, paintw = width(), painth = height();
@ -370,7 +378,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
_animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg);
}
} else if (preview) {
paintPriceTag(p, rthumb);
drawPriceTag(p, rthumb, context, [&] {
return _spoiler ? _spoiler->background : QImage();
});
}
if (showEnlarge) {
auto hq = PainterHighQualityEnabler(p);
@ -403,41 +413,93 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
}
}
void Photo::paintPriceTag(Painter &p, QRect rthumb) const {
void Photo::setupPriceTag() const {
const auto media = parent()->data()->media();
const auto invoice = media ? media->invoice() : nullptr;
const auto price = invoice->isPaidMedia ? invoice->amount : 0;
if (!price) {
return;
}
_priceTag = std::make_unique<PriceTag>();
_priceTag->price = price;
}
auto text = Ui::Text::String();
text.setText(
st::semiboldTextStyle,
tr::lng_paid_price(
tr::now,
lt_price,
QChar(0x2B50) + Lang::FormatCountDecimal(invoice->amount)));
const auto width = text.maxWidth();
const auto inner = QRect(0, 0, width, text.minHeight());
const auto outer = inner.marginsAdded(st::paidTagPadding);
const auto size = outer.size();
void Photo::drawPriceTag(
Painter &p,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const {
if (!_priceTag) {
setupPriceTag();
if (!_priceTag) {
return;
}
}
const auto st = context.st;
const auto darken = st->msgDateImgBg()->c;
const auto fg = st->msgDateImgFg()->c;
if (_priceTag->cache.isNull()
|| _priceTag->darken != darken
|| _priceTag->fg != fg) {
auto bg = generateBackground();
if (bg.isNull()) {
bg = QImage(2, 2, QImage::Format_ARGB32_Premultiplied);
bg.fill(Qt::black);
}
auto hq = PainterHighQualityEnabler(p);
p.setBrush(st::toastBg);
p.setPen(Qt::NoPen);
auto text = Ui::Text::String();
text.setText(
st::semiboldTextStyle,
tr::lng_paid_price(
tr::now,
lt_price,
QChar(0x2B50) + Lang::FormatCountDecimal(_priceTag->price)));
const auto width = text.maxWidth();
const auto inner = QRect(0, 0, width, text.minHeight());
const auto outer = inner.marginsAdded(st::paidTagPadding);
const auto size = outer.size();
const auto radius = std::min(size.width(), size.height()) / 2;
const auto ratio = style::DevicePixelRatio();
auto cache = QImage(
size * ratio,
QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(ratio);
cache.fill(Qt::black);
auto p = Painter(&cache);
auto hq = PainterHighQualityEnabler(p);
p.drawImage(
QRect(
(size.width() - rthumb.width()) / 2,
(size.height() - rthumb.height()) / 2,
rthumb.width(),
rthumb.height()),
bg);
p.fillRect(QRect(QPoint(), size), darken);
p.setPen(fg);
text.draw(p, -outer.x(), -outer.y(), width);
p.end();
const auto radius = std::min(size.width(), size.height()) / 2.;
const auto rect = QRect(
rthumb.x() + (rthumb.width() - size.width()) / 2,
rthumb.y() + (rthumb.height() - size.height()) / 2,
size.width(),
size.height());
p.drawRoundedRect(rect, radius, radius);
p.setPen(st::toastFg);
text.draw(p, rect.x() - outer.x(), rect.y() - outer.y(), width);
_priceTag->darken = darken;
_priceTag->fg = fg;
_priceTag->cache = Images::Round(
std::move(cache),
Images::CornersMask(radius));
}
const auto &cache = _priceTag->cache;
const auto size = cache.size() / cache.devicePixelRatio();
const auto left = rthumb.x() + (rthumb.width() - size.width()) / 2;
const auto top = rthumb.y() + (rthumb.height() - size.height()) / 2;
p.drawImage(left, top, cache);
if (context.selected()) {
auto hq = PainterHighQualityEnabler(p);
const auto radius = std::min(size.width(), size.height()) / 2;
p.setPen(Qt::NoPen);
p.setBrush(st->msgSelectOverlay());
p.drawRoundedRect(
QRect(left, top, size.width(), size.height()),
radius,
radius);
}
}
void Photo::validateUserpicImageCache(QSize size, bool forum) const {
@ -647,12 +709,24 @@ QRect Photo::enlargeRect() const {
};
}
ClickHandlerPtr Photo::ensureExtendedMediaLink() const {
ClickHandlerPtr Photo::priceTagLink() const {
const auto item = parent()->data();
if (!_extendedMediaLink && item->isRegular()) {
_extendedMediaLink = MakePaidMediaLink(item);
if (!item->isRegular()) {
return nullptr;
} else if (!_priceTag) {
setupPriceTag();
if (!_priceTag) {
return nullptr;
}
}
return _extendedMediaLink;
if (!_priceTag->link) {
_priceTag->link = MakePaidMediaLink(item);
}
return _priceTag->link;
}
QImage Photo::priceTagBackground() const {
return _spoiler ? _spoiler->background : QImage();
}
TextState Photo::textState(QPoint point, StateRequest request) const {
@ -669,7 +743,7 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
if (QRect(paintx, painty, paintw, painth).contains(point)) {
ensureDataMediaCreated();
result.link = _data->extendedMediaPreview()
? ensureExtendedMediaLink()
? priceTagLink()
: (_spoiler && !_spoiler->revealed)
? _spoiler->link
: _data->uploading()
@ -735,8 +809,9 @@ void Photo::drawGrouped(
const auto st = context.st;
const auto sti = context.imageStyle();
const auto loaded = _dataMedia->loaded();
const auto displayLoading = _data->displayLoading();
const auto preview = _data->extendedMediaPreview();
const auto loaded = preview || _dataMedia->loaded();
const auto displayLoading = !preview && _data->displayLoading();
if (displayLoading) {
ensureAnimation();
@ -841,7 +916,9 @@ TextState Photo::getStateGrouped(
return {};
}
ensureDataMediaCreated();
return TextState(_parent, (_spoiler && !_spoiler->revealed)
return TextState(_parent, _data->extendedMediaPreview()
? priceTagLink()
: (_spoiler && !_spoiler->revealed)
? _spoiler->link
: _data->uploading()
? _cancell
@ -887,7 +964,8 @@ void Photo::validateGroupedCache(
ensureDataMediaCreated();
const auto loaded = _dataMedia->loaded();
const auto preview = _data->extendedMediaPreview();
const auto loaded = preview || _dataMedia->loaded();
const auto loadLevel = loaded
? 2
: (_dataMedia->thumbnailInline()

View file

@ -75,6 +75,14 @@ public:
QPoint point,
StateRequest request) const override;
void drawPriceTag(
Painter &p,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const override;
ClickHandlerPtr priceTagLink() const override;
QImage priceTagBackground() const override;
void hideSpoilers() override;
bool needsBubble() const override;
bool customInfoLayout() const override {
@ -97,6 +105,7 @@ protected:
private:
struct Streamed;
struct PriceTag;
void create(FullMsgId contextId, PeerData *chat = nullptr);
@ -106,6 +115,7 @@ private:
void ensureDataMediaCreated() const;
void dataMediaCreated() const;
void setupPriceTag() const;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
@ -147,13 +157,11 @@ private:
[[nodiscard]] QSize photoSize() const;
[[nodiscard]] QRect enlargeRect() const;
void paintPriceTag(Painter &p, QRect rthumb) const;
[[nodiscard]] ClickHandlerPtr ensureExtendedMediaLink() const;
void togglePollingStory(bool enabled) const;
const not_null<PhotoData*> _data;
const FullStoryId _storyId;
mutable ClickHandlerPtr _extendedMediaLink;
mutable std::unique_ptr<PriceTag> _priceTag;
mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
mutable std::unique_ptr<Streamed> _streamed;
const std::unique_ptr<MediaSpoiler> _spoiler;