Update API scheme to layer 183. Paid media.

This commit is contained in:
John Preston 2024-06-18 14:02:05 +04:00
parent e71a067f4b
commit 3ece9b1566
25 changed files with 227 additions and 533 deletions

View file

@ -727,8 +727,6 @@ PRIVATE
history/view/media/history_view_dice.h
history/view/media/history_view_document.cpp
history/view/media/history_view_document.h
history/view/media/history_view_extended_preview.cpp
history/view/media/history_view_extended_preview.h
history/view/media/history_view_file.cpp
history/view/media/history_view_file.h
history/view/media/history_view_game.cpp

View file

@ -1696,7 +1696,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
const auto peerId = peerFromMTP(d.vpeer());
const auto msgId = d.vmsg_id().v;
if (const auto item = session().data().message(peerId, msgId)) {
item->applyEdition(d.vextended_media());
item->applyEdition(d.vextended_media().v);
}
} break;

View file

@ -7,12 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_media_types.h"
#include "base/random.h"
#include "history/history.h"
#include "history/history_item.h" // CreateMedia.
#include "history/history_location_manager.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_item_preview.h"
#include "history/view/media/history_view_extended_preview.h"
#include "history/view/media/history_view_photo.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_gif.h"
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_giveaway.h"
#include "history/view/media/history_view_invoice.h"
#include "history/view/media/history_view_media_generic.h"
#include "history/view/media/history_view_media_grouped.h"
#include "history/view/media/history_view_call.h"
#include "history/view/media/history_view_web_page.h"
#include "history/view/media/history_view_poll.h"
@ -261,48 +262,80 @@ template <typename MediaType>
}
bool UpdateExtendedMedia(
Invoice &invoice,
std::unique_ptr<Media> &media,
not_null<HistoryItem*> item,
const MTPMessageExtendedMedia &media) {
return media.match([&](const MTPDmessageExtendedMediaPreview &data) {
if (invoice.extendedMedia) {
return false;
const MTPMessageExtendedMedia &extended) {
return extended.match([&](const MTPDmessageExtendedMediaPreview &data) {
auto photo = (PhotoData*)nullptr;
if (!media) {
const auto id = base::RandomValue<PhotoId>();
photo = item->history()->owner().photo(id);
} else {
photo = media->photo();
if (!photo || !photo->extendedMediaPreview()) {
return false;
}
}
auto changed = false;
auto &preview = invoice.extendedPreview;
auto size = QSize();
auto thumbnail = QByteArray();
auto videoDuration = TimeId();
if (const auto &w = data.vw()) {
const auto &h = data.vh();
Assert(h.has_value());
const auto dimensions = QSize(w->v, h->v);
if (preview.dimensions != dimensions) {
preview.dimensions = dimensions;
size = QSize(w->v, h->v);
if (!changed && photo->size(PhotoSize::Large) != size) {
changed = true;
}
}
if (const auto &thumb = data.vthumb()) {
if (thumb->type() == mtpc_photoStrippedSize) {
const auto bytes = thumb->c_photoStrippedSize().vbytes().v;
if (preview.inlineThumbnailBytes != bytes) {
preview.inlineThumbnailBytes = bytes;
thumbnail = thumb->c_photoStrippedSize().vbytes().v;
if (!changed && photo->inlineThumbnailBytes() != thumbnail) {
changed = true;
}
}
}
if (const auto &duration = data.vvideo_duration()) {
if (preview.videoDuration != duration->v) {
preview.videoDuration = duration->v;
videoDuration = duration->v;
if (photo->extendedMediaVideoDuration() != videoDuration) {
changed = true;
}
}
if (changed) {
photo->setExtendedMediaPreview(size, thumbnail, videoDuration);
}
if (!media) {
media = std::make_unique<MediaPhoto>(item, photo, true);
}
return changed;
}, [&](const MTPDmessageExtendedMedia &data) {
invoice.extendedMedia = HistoryItem::CreateMedia(
item,
data.vmedia());
media = HistoryItem::CreateMedia(item, data.vmedia());
return true;
});
}
bool UpdateExtendedMedia(
Invoice &invoice,
not_null<HistoryItem*> item,
const QVector<MTPMessageExtendedMedia> &media) {
auto changed = false;
const auto count = int(media.size());
for (auto i = 0; i != count; ++i) {
if (i < invoice.extendedMedia.size()) {
invoice.extendedMedia.emplace_back();
changed = true;
}
UpdateExtendedMedia(invoice.extendedMedia[i], item, media[i]);
}
if (count < invoice.extendedMedia.size()) {
invoice.extendedMedia.resize(count);
changed = true;
}
return changed;
}
TextForMimeData WithCaptionClipboardText(
const QString &attachType,
TextForMimeData &&caption) {
@ -344,11 +377,22 @@ Invoice ComputeInvoiceData(
.isTest = data.is_test(),
};
if (const auto &media = data.vextended_media()) {
UpdateExtendedMedia(result, item, *media);
UpdateExtendedMedia(result, item, { *media });
}
return result;
}
Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
const MTPDmessageMediaPaidMedia &data) {
auto result = Invoice{
.amount = data.vstars_amount().v,
.currency = Ui::kCreditsCurrency,
};
UpdateExtendedMedia(result, item, data.vextended_media().v);
return result;
}
Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
auto result = Call();
result.finishReason = [&] {
@ -424,6 +468,18 @@ GiveawayResults ComputeGiveawayResultsData(
return result;
}
bool HasExtendedMedia(const Invoice &invoice) {
return !invoice.extendedMedia.empty();
}
bool HasUnpaidMedia(const Invoice &invoice) {
for (const auto &media : invoice.extendedMedia) {
const auto photo = media->photo();
return photo && photo->extendedMediaPreview();
}
return false;
}
Media::Media(not_null<HistoryItem*> parent) : _parent(parent) {
}
@ -1851,14 +1907,14 @@ MediaInvoice::MediaInvoice(
.currency = data.currency,
.title = data.title,
.description = data.description,
.extendedPreview = data.extendedPreview,
.extendedMedia = (data.extendedMedia
? data.extendedMedia->clone(parent)
: nullptr),
.photo = data.photo,
.isTest = data.isTest,
} {
if (_invoice.extendedPreview && !_invoice.extendedMedia) {
_invoice.extendedMedia.reserve(data.extendedMedia.size());
for (auto &item : data.extendedMedia) {
_invoice.extendedMedia.push_back(item->clone(parent));
}
if (HasUnpaidMedia(_invoice)) {
Ui::PreloadImageSpoiler();
}
}
@ -1917,7 +1973,7 @@ bool MediaInvoice::updateSentMedia(const MTPMessageMedia &media) {
bool MediaInvoice::updateExtendedMedia(
not_null<HistoryItem*> item,
const MTPMessageExtendedMedia &media) {
const QVector<MTPMessageExtendedMedia> &media) {
Expects(item == parent());
return UpdateExtendedMedia(_invoice, item, media);
@ -1927,15 +1983,15 @@ std::unique_ptr<HistoryView::Media> MediaInvoice::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
if (_invoice.extendedMedia) {
return _invoice.extendedMedia->createView(
if (_invoice.extendedMedia.size() == 1) {
return _invoice.extendedMedia.front()->createView(
message,
realParent,
replacing);
} else if (_invoice.extendedPreview) {
return std::make_unique<HistoryView::ExtendedPreview>(
} else if (!_invoice.extendedMedia.empty()) {
return std::make_unique<HistoryView::GroupedMedia>(
message,
&_invoice);
_invoice.extendedMedia);
}
return std::make_unique<HistoryView::Invoice>(message, &_invoice);
}

View file

@ -84,19 +84,6 @@ struct Call {
};
struct ExtendedPreview {
QByteArray inlineThumbnailBytes;
QSize dimensions;
TimeId videoDuration = -1;
[[nodiscard]] bool empty() const {
return dimensions.isEmpty();
}
explicit operator bool() const {
return !empty();
}
};
class Media;
struct Invoice {
@ -105,11 +92,12 @@ struct Invoice {
QString currency;
QString title;
TextWithEntities description;
ExtendedPreview extendedPreview;
std::unique_ptr<Media> extendedMedia;
std::vector<std::unique_ptr<Media>> extendedMedia;
PhotoData *photo = nullptr;
bool isTest = false;
};
[[nodiscard]] bool HasExtendedMedia(const Invoice &invoice);
[[nodiscard]] bool HasUnpaidMedia(const Invoice &invoice);
struct GiveawayStart {
std::vector<not_null<ChannelData*>> channels;
@ -207,7 +195,7 @@ public:
virtual bool updateSentMedia(const MTPMessageMedia &media) = 0;
virtual bool updateExtendedMedia(
not_null<HistoryItem*> item,
const MTPMessageExtendedMedia &media) {
const QVector<MTPMessageExtendedMedia> &media) {
return false;
}
virtual std::unique_ptr<HistoryView::Media> createView(
@ -524,7 +512,7 @@ public:
bool updateSentMedia(const MTPMessageMedia &media) override;
bool updateExtendedMedia(
not_null<HistoryItem*> item,
const MTPMessageExtendedMedia &media) override;
const QVector<MTPMessageExtendedMedia> &media) override;
std::unique_ptr<HistoryView::Media> createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
@ -750,6 +738,9 @@ private:
[[nodiscard]] Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
const MTPDmessageMediaInvoice &data);
[[nodiscard]] Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
const MTPDmessageMediaPaidMedia &data);
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);

View file

@ -50,6 +50,38 @@ PhotoData::~PhotoData() {
base::take(_videoSizes);
}
void PhotoData::setFields(TimeId date, bool hasAttachedStickers) {
_dateOrExtendedVideoDuration = date;
_hasStickers = hasAttachedStickers;
_extendedMediaPreview = false;
}
void PhotoData::setExtendedMediaPreview(
QSize dimensions,
const QByteArray &inlineThumbnailBytes,
TimeId videoDuration) {
_extendedMediaPreview = true;
updateImages(
inlineThumbnailBytes,
{},
{},
{ .location = { {}, dimensions.width(), dimensions.height() } },
{},
{},
{});
_dateOrExtendedVideoDuration = videoDuration + 1;
}
bool PhotoData::extendedMediaPreview() const {
return _extendedMediaPreview;
}
std::optional<TimeId> PhotoData::extendedMediaVideoDuration() const {
return (_extendedMediaPreview && _dateOrExtendedVideoDuration)
? TimeId(_dateOrExtendedVideoDuration - 1)
: std::optional<TimeId>();
}
Data::Session &PhotoData::owner() const {
return *_owner;
}
@ -74,6 +106,10 @@ void PhotoData::load(
load(PhotoSize::Large, origin, fromCloud, autoLoading);
}
TimeId PhotoData::date() const {
return _extendedMediaPreview ? 0 : _dateOrExtendedVideoDuration;
}
bool PhotoData::loading() const {
return loading(PhotoSize::Large);
}

View file

@ -53,6 +53,7 @@ public:
void automaticLoadSettingsChanged();
[[nodiscard]] TimeId date() const;
[[nodiscard]] bool loading() const;
[[nodiscard]] bool displayLoading() const;
void cancel();
@ -89,6 +90,14 @@ public:
[[nodiscard]] auto activeMediaView() const
-> std::shared_ptr<Data::PhotoMedia>;
void setFields(TimeId date, bool hasAttachedStickers);
void setExtendedMediaPreview(
QSize dimensions,
const QByteArray &inlineThumbnailBytes,
TimeId videoDuration);
[[nodiscard]] bool extendedMediaPreview() const;
[[nodiscard]] std::optional<TimeId> extendedMediaVideoDuration() const;
void updateImages(
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &small,
@ -148,11 +157,10 @@ public:
void setHasAttachedStickers(bool value);
// For now they return size of the 'large' image.
int width() const;
int height() const;
[[nodiscard]] int width() const;
[[nodiscard]] int height() const;
PhotoId id = 0;
TimeId date = 0;
PeerData *peer = nullptr; // for chat and channel photos connection
// geo, caption
@ -164,6 +172,8 @@ private:
[[nodiscard]] const Data::CloudFile &videoFile(
Data::PhotoSize size) const;
TimeId _dateOrExtendedVideoDuration = 0;
struct VideoSizes {
Data::CloudFile small;
Data::CloudFile large;
@ -177,6 +187,8 @@ private:
int32 _dc = 0;
uint64 _access = 0;
bool _hasStickers = false;
bool _extendedMediaPreview = false;
QByteArray _fileReference;
std::unique_ptr<Data::ReplyPreview> _replyPreview;
std::weak_ptr<Data::PhotoMedia> _media;

View file

@ -3077,8 +3077,7 @@ void Session::photoApplyFields(
return;
}
photo->setRemoteLocation(dc, access, fileReference);
photo->date = date;
photo->setHasAttachedStickers(hasStickers);
photo->setFields(date, hasStickers);
photo->updateImages(
inlineThumbnailBytes,
small,

View file

@ -666,6 +666,28 @@ Invoice ParseInvoice(const MTPDmessageMediaInvoice &data) {
return result;
}
PaidMedia ParsePaidMedia(
ParseMediaContext &context,
const MTPDmessageMediaPaidMedia &data,
const QString &folder,
TimeId date) {
auto result = PaidMedia();
result.stars = data.vstars_amount().v;
result.extended.reserve(data.vextended_media().v.size());
for (const auto &extended : data.vextended_media().v) {
result.extended.push_back(extended.match([](
const MTPDmessageExtendedMediaPreview &)
-> std::unique_ptr<Media> {
return std::unique_ptr<Media>();
}, [&](const MTPDmessageExtendedMedia &data)
-> std::unique_ptr<Media> {
return std::make_unique<Media>(
ParseMedia(context, data.vmedia(), folder, date));
}));
}
return result;
}
Poll ParsePoll(const MTPDmessageMediaPoll &data) {
auto result = Poll();
data.vpoll().match([&](const MTPDpoll &poll) {
@ -1225,6 +1247,8 @@ Media ParseMedia(
result.content = ParseGiveaway(data);
}, [&](const MTPDmessageMediaGiveawayResults &data) {
// #TODO export giveaway
}, [&](const MTPDmessageMediaPaidMedia &data) {
result.content = ParsePaidMedia(context, data, folder, date);
}, [](const MTPDmessageMediaEmpty &data) {});
return result;
}

View file

@ -182,6 +182,18 @@ struct Invoice {
int32 receiptMsgId = 0;
};
struct Media;
struct PaidMedia {
PaidMedia() = default;
PaidMedia(PaidMedia &&) = default;
PaidMedia &operator=(PaidMedia &&) = default;
PaidMedia(const PaidMedia &) = delete;
PaidMedia &operator=(const PaidMedia &) = delete;
uint64 stars = 0;
std::vector<std::unique_ptr<Media>> extended;
};
struct Poll {
struct Answer {
Utf8String text;
@ -337,6 +349,7 @@ struct Media {
Invoice,
Poll,
GiveawayStart,
PaidMedia,
UnsupportedMedia> content;
TimeId ttl = 0;

View file

@ -2092,6 +2092,9 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
result.status = Data::FormatMoneyAmount(data.amount, data.currency);
}, [](const Poll &data) {
}, [](const GiveawayStart &data) {
}, [&](const PaidMedia &data) {
result.classes = "media_invoice";
result.status = Data::FormatMoneyAmount(data.stars, "XTR");
}, [](const UnsupportedMedia &data) {
Unexpected("Unsupported message.");
}, [](v::null_t) {});

View file

@ -779,6 +779,8 @@ QByteArray SerializeMessage(
{ "until_date", SerializeDate(data.untilDate) },
{ "channels", serialized },
}));
}, [&](const PaidMedia &data) {
push("paid_stars_amount", data.stars);
}, [](const UnsupportedMedia &data) {
Unexpected("Unsupported message.");
}, [](v::null_t) {});

View file

@ -1031,7 +1031,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
}
}
session().data().reactions().poll(item, context.now);
if (item->hasExtendedMediaPreview()) {
if (item->hasUnpaidContent()) {
session().api().views().pollExtendedMedia(item);
}
_reactionsManager->recordCurrentReactionEffect(

View file

@ -349,6 +349,10 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
return std::make_unique<Data::MediaGiveawayResults>(
item,
Data::ComputeGiveawayResultsData(item, media));
}, [&](const MTPDmessageMediaPaidMedia &media) -> Result {
return std::make_unique<Data::MediaInvoice>(
item,
Data::ComputeInvoiceData(item, media));
}, [](const MTPDmessageMediaEmpty &) -> Result {
return nullptr;
}, [](const MTPDmessageMediaUnsupported &) -> Result {
@ -1813,7 +1817,8 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) {
}
}
void HistoryItem::applyEdition(const MTPMessageExtendedMedia &media) {
void HistoryItem::applyEdition(
const QVector<MTPMessageExtendedMedia> &media) {
if (const auto existing = this->media()) {
if (existing->updateExtendedMedia(this, media)) {
checkBuyButton();
@ -2241,7 +2246,7 @@ bool HistoryItem::forbidsSaving() const {
if (forbidsForward()) {
return true;
} else if (const auto invoice = _media ? _media->invoice() : nullptr) {
return (invoice->extendedMedia != nullptr);
return HasExtendedMedia(*invoice);
}
return false;
}
@ -2991,10 +2996,10 @@ bool HistoryItem::externalReply() const {
return false;
}
bool HistoryItem::hasExtendedMediaPreview() const {
bool HistoryItem::hasUnpaidContent() const {
if (const auto media = _media.get()) {
if (const auto invoice = media->invoice()) {
return (invoice->extendedPreview && !invoice->extendedMedia);
return HasUnpaidMedia(*invoice);
}
}
return false;
@ -3778,7 +3783,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) {
void HistoryItem::refreshMedia(const MTPMessageMedia *media) {
const auto was = (_media != nullptr);
if (const auto invoice = was ? _media->invoice() : nullptr) {
if (invoice->extendedMedia) {
if (HasExtendedMedia(*invoice)) {
return;
}
}

View file

@ -326,7 +326,7 @@ public:
[[nodiscard]] int repliesCount() const;
[[nodiscard]] bool repliesAreComments() const;
[[nodiscard]] bool externalReply() const;
[[nodiscard]] bool hasExtendedMediaPreview() const;
[[nodiscard]] bool hasUnpaidContent() const;
[[nodiscard]] bool inHighlightProcess() const;
void highlightProcessDone();
@ -345,7 +345,7 @@ public:
void applyChanges(not_null<Data::Story*> story);
void applyEdition(const MTPDmessageService &message);
void applyEdition(const MTPMessageExtendedMedia &media);
void applyEdition(const QVector<MTPMessageExtendedMedia> &media);
void updateForwardedInfo(const MTPMessageFwdHeader *fwd);
void updateSentContent(
const TextWithEntities &textWithEntities,

View file

@ -1091,8 +1091,8 @@ void HistoryMessageReplyMarkup::updateData(
bool HistoryMessageReplyMarkup::hiddenBy(Data::Media *media) const {
if (media && (data.flags & ReplyMarkupFlag::OnlyBuyButton)) {
if (const auto invoice = media->invoice()) {
if (invoice->extendedPreview
&& (!invoice->extendedMedia || !invoice->receiptMsgId)) {
if (HasUnpaidMedia(*invoice)
|| (HasExtendedMedia(*invoice) && !invoice->receiptMsgId)) {
return true;
}
}

View file

@ -557,6 +557,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
return Result::Good;
}, [](const MTPDmessageMediaGiveawayResults &) {
return Result::Good;
}, [](const MTPDmessageMediaPaidMedia &) {
return Result::Good;
}, [](const MTPDmessageMediaUnsupported &) {
return Result::Unsupported;
});

View file

@ -2263,7 +2263,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
}
}
session->data().reactions().poll(item, context.now);
if (item->hasExtendedMediaPreview()) {
if (item->hasUnpaidContent()) {
session->api().views().pollExtendedMedia(item);
}
if (_reactionsManager) {

View file

@ -1399,7 +1399,7 @@ CopyRestrictionType ScheduledWidget::listCopyMediaRestrictionType(
not_null<HistoryItem*> item) {
if (const auto media = item->media()) {
if (const auto invoice = media->invoice()) {
if (invoice->extendedMedia) {
if (HasExtendedMedia(*invoice)) {
return CopyMediaRestrictionTypeFor(_history->peer, item);
}
}

View file

@ -1,366 +0,0 @@
/*
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_extended_preview.h"
#include "history/history_item.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/media/history_view_media_common.h"
#include "media/streaming/media_streaming_utility.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/image/image.h"
#include "ui/image/image_prepare.h"
#include "ui/chat/chat_style.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "data/data_session.h"
#include "payments/payments_checkout_process.h"
#include "payments/payments_non_panel_process.h"
#include "window/window_session_controller.h"
#include "mainwindow.h"
#include "core/click_handler_types.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
[[nodiscard]] ClickHandlerPtr MakeInvoiceLink(not_null<HistoryItem*> item) {
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
Payments::CheckoutProcess::Start(
item,
Payments::Mode::Payment,
(controller
? crl::guard(
controller,
[=](auto) { controller->widget()->activate(); })
: Fn<void(Payments::CheckoutResult)>()),
(controller
? Payments::ProcessNonPanelPaymentFormFactory(
controller,
item)
: nullptr));
});
}
} // namespace
ExtendedPreview::ExtendedPreview(
not_null<Element*> parent,
not_null<Data::Invoice*> invoice)
: Media(parent)
, _invoice(invoice) {
const auto item = parent->data();
_spoiler.link = MakeInvoiceLink(item);
resolveButtonText();
}
void ExtendedPreview::resolveButtonText() {
if (const auto markup = _parent->data()->inlineReplyMarkup()) {
for (const auto &row : markup->data.rows) {
for (const auto &button : row) {
if (button.type == HistoryMessageMarkupButton::Type::Buy) {
_buttonText.setText(
st::semiboldTextStyle,
TextUtilities::SingleLine(button.text));
return;
}
}
}
}
}
ExtendedPreview::~ExtendedPreview() {
if (hasHeavyPart()) {
unloadHeavyPart();
_parent->checkHeavyPart();
}
}
void ExtendedPreview::ensureThumbnailRead() const {
if (!_inlineThumbnail.isNull() || _imageCacheInvalid) {
return;
}
const auto &bytes = _invoice->extendedPreview.inlineThumbnailBytes;
if (bytes.isEmpty()) {
return;
}
_inlineThumbnail = Images::FromInlineBytes(bytes);
if (_inlineThumbnail.isNull()) {
_imageCacheInvalid = true;
} else {
history()->owner().registerHeavyViewPart(_parent);
}
}
bool ExtendedPreview::hasHeavyPart() const {
return _spoiler.animation || !_inlineThumbnail.isNull();
}
void ExtendedPreview::unloadHeavyPart() {
_inlineThumbnail
= _spoiler.background
= _spoiler.cornerCache
= _buttonBackground = QImage();
_spoiler.animation = nullptr;
}
bool ExtendedPreview::enforceBubbleWidth() const {
return true;
}
QSize ExtendedPreview::countOptimalSize() {
const auto &preview = _invoice->extendedPreview;
const auto dimensions = preview.dimensions;
const auto minWidth = std::min(
std::max({
_parent->minWidthForMedia(),
(_parent->hasBubble()
? st::historyPhotoBubbleMinWidth
: st::minPhotoSize),
minWidthForButton(),
}),
st::maxMediaSize);
const auto scaled = CountDesiredMediaSize(dimensions);
auto maxWidth = qMax(scaled.width(), minWidth);
auto minHeight = qMax(scaled.height(), st::minPhotoSize);
if (preview.videoDuration < 0) {
accumulate_max(maxWidth, scaled.height());
}
return { maxWidth, minHeight };
}
QSize ExtendedPreview::countCurrentSize(int newWidth) {
const auto &preview = _invoice->extendedPreview;
const auto dimensions = preview.dimensions;
const auto thumbMaxWidth = std::min(newWidth, st::maxMediaSize);
const auto minWidth = std::min(
std::max({
_parent->minWidthForMedia(),
(_parent->hasBubble()
? st::historyPhotoBubbleMinWidth
: st::minPhotoSize),
minWidthForButton(),
}),
thumbMaxWidth);
const auto scaled = (preview.videoDuration >= 0)
? CountMediaSize(
CountDesiredMediaSize(dimensions),
newWidth)
: CountPhotoMediaSize(
CountDesiredMediaSize(dimensions),
newWidth,
maxWidth());
newWidth = qMax(scaled.width(), minWidth);
auto newHeight = qMax(scaled.height(), st::minPhotoSize);
if (_parent->hasBubble()) {
const auto maxWithCaption = qMin(
st::msgMaxWidth,
_parent->textualMaxWidth());
newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth);
}
if (newWidth >= maxWidth()) {
newHeight = qMin(newHeight, minHeight());
}
return { newWidth, newHeight };
}
int ExtendedPreview::minWidthForButton() const {
return (st::msgBotKbButton.margin + st::msgBotKbButton.padding) * 2
+ _buttonText.maxWidth();
}
void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintx = 0, painty = 0, paintw = width(), painth = height();
auto bubble = _parent->hasBubble();
auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
const auto inWebPage = (_parent->media() != this);
const auto rounding = inWebPage
? std::optional<Ui::BubbleRounding>()
: adjustedBubbleRounding();
if (!bubble) {
Assert(rounding.has_value());
fillImageShadow(p, rthumb, *rounding, context);
}
validateImageCache(rthumb.size(), rounding);
p.drawImage(rthumb.topLeft(), _spoiler.background);
fillImageSpoiler(p, &_spoiler, rthumb, context);
paintButton(p, rthumb, context);
if (context.selected()) {
fillImageOverlay(p, rthumb, rounding, context);
}
// date
if (!inWebPage) {
auto fullRight = paintx + paintw;
auto fullBottom = painty + painth;
if (needInfoDisplay()) {
_parent->drawInfo(
p,
context,
fullRight,
fullBottom,
2 * paintx + paintw,
InfoDisplayType::Image);
}
if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) {
auto fastShareLeft = _parent->hasRightLayout()
? (paintx - size->width() - st::historyFastShareLeft)
: (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height());
_parent->drawRightAction(p, context, fastShareLeft, fastShareTop, 2 * paintx + paintw);
}
}
}
void ExtendedPreview::validateImageCache(
QSize outer,
std::optional<Ui::BubbleRounding> rounding) const {
const auto ratio = style::DevicePixelRatio();
if (_spoiler.background.size() == (outer * ratio)
&& _spoiler.backgroundRounding == rounding) {
return;
}
_spoiler.background = Images::Round(
prepareImageCache(outer),
MediaRoundingMask(rounding));
_spoiler.backgroundRounding = rounding;
}
QImage ExtendedPreview::prepareImageCache(QSize outer) const {
ensureThumbnailRead();
return PrepareWithBlurredBackground(outer, {}, {}, _inlineThumbnail);
}
void ExtendedPreview::paintButton(
Painter &p,
QRect outer,
const PaintContext &context) const {
const auto st = context.st;
const auto &padding = st::extendedPreviewButtonPadding;
const auto margin = st::extendedPreviewButtonMargin;
const auto width = std::min(
_buttonText.maxWidth() + padding.left() + padding.right(),
outer.width() - 2 * margin);
const auto height = padding.top()
+ st::semiboldFont->height
+ padding.bottom();
const auto overlay = st->msgDateImgBg()->c;
const auto ratio = style::DevicePixelRatio();
const auto size = QSize(width, height);
if (_buttonBackground.size() != size * ratio
|| _buttonBackgroundOverlay != overlay) {
auto &background = _spoiler.background;
if (background.width() < width * ratio
|| background.height() < height * ratio) {
return;
}
_buttonBackground = background.copy(QRect(
(background.width() - width * ratio) / 2,
(background.height() - height * ratio) / 2,
width * ratio,
height * ratio));
_buttonBackground.setDevicePixelRatio(ratio);
auto p = QPainter(&_buttonBackground);
p.fillRect(0, 0, width, height, overlay);
p.end();
_buttonBackground = Images::Round(
std::move(_buttonBackground),
Images::CornersMask(height / 2));
}
const auto left = outer.x() + (outer.width() - width) / 2;
const auto top = outer.y() + (outer.height() - height) / 2;
p.drawImage(left, top, _buttonBackground);
p.setPen(st->msgDateImgFg()->c);
_buttonText.drawLeftElided(
p,
left + padding.left(),
top + padding.top(),
width - padding.left() - padding.right(),
outer.width());
}
TextState ExtendedPreview::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result;
}
auto paintx = 0, painty = 0, paintw = width(), painth = height();
auto bubble = _parent->hasBubble();
if (QRect(paintx, painty, paintw, painth).contains(point)) {
result.link = _spoiler.link;
}
if (!bubble && _parent->media() == this) {
auto fullRight = paintx + paintw;
auto fullBottom = painty + painth;
const auto bottomInfoResult = _parent->bottomInfoTextState(
fullRight,
fullBottom,
point,
InfoDisplayType::Image);
if (bottomInfoResult.link
|| bottomInfoResult.cursor != CursorState::None
|| bottomInfoResult.customTooltip) {
return bottomInfoResult;
}
if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) {
auto fastShareLeft = _parent->hasRightLayout()
? (paintx - size->width() - st::historyFastShareLeft)
: (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height());
if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) {
result.link = _parent->rightActionLink(point
- QPoint(fastShareLeft, fastShareTop));
}
}
}
return result;
}
bool ExtendedPreview::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const {
return p == _spoiler.link;
}
bool ExtendedPreview::dragItemByHandler(const ClickHandlerPtr &p) const {
return p == _spoiler.link;
}
bool ExtendedPreview::needInfoDisplay() const {
return _parent->data()->isSending()
|| _parent->data()->hasFailed()
|| _parent->isUnderCursor()
|| (_parent->delegate()->elementContext() == Context::ChatPreview)
|| _parent->isLastAndSelfMessage();
}
bool ExtendedPreview::needsBubble() const {
const auto item = _parent->data();
return !item->isService()
&& (item->repliesAreComments()
|| item->externalReply()
|| item->viaBot()
|| !item->emptyText()
|| _parent->displayReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName()
|| _parent->displayedTopicButton());
}
QPoint ExtendedPreview::resolveCustomInfoRightBottom() const {
const auto skipx = (st::msgDateImgDelta + st::msgDateImgPadding.x());
const auto skipy = (st::msgDateImgDelta + st::msgDateImgPadding.y());
return QPoint(width() - skipx, height() - skipy);
}
} // namespace HistoryView

View file

@ -1,87 +0,0 @@
/*
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
*/
#pragma once
#include "history/view/media/history_view_media.h"
#include "history/view/media/history_view_media_spoiler.h"
enum class ImageRoundRadius;
namespace Ui {
class SpoilerAnimation;
} // namespace Ui
namespace Data {
struct Invoice;
} // namespace Data
namespace HistoryView {
class Element;
class ExtendedPreview final : public Media {
public:
ExtendedPreview(
not_null<Element*> parent,
not_null<Data::Invoice*> invoice);
~ExtendedPreview();
bool hideMessageText() const override {
return false;
}
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
[[nodiscard]] bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const override;
[[nodiscard]] bool dragItemByHandler(
const ClickHandlerPtr &p) const override;
bool needsBubble() const override;
bool customInfoLayout() const override {
return true;
}
QPoint resolveCustomInfoRightBottom() const override;
bool skipBubbleTail() const override {
return isRoundedInBubbleBottom();
}
bool hasHeavyPart() const override;
void unloadHeavyPart() override;
bool enforceBubbleWidth() const override;
private:
int minWidthForButton() const;
void resolveButtonText();
void ensureThumbnailRead() const;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
bool needInfoDisplay() const;
void validateImageCache(
QSize outer,
std::optional<Ui::BubbleRounding> rounding) const;
[[nodiscard]] QImage prepareImageCache(QSize outer) const;
void paintButton(
Painter &p,
QRect outer,
const PaintContext &context) const;
const not_null<Data::Invoice*> _invoice;
mutable MediaSpoiler _spoiler;
mutable QImage _inlineThumbnail;
mutable QImage _buttonBackground;
mutable QColor _buttonBackgroundOverlay;
mutable Ui::Text::String _buttonText;
mutable bool _imageCacheInvalid = false;
};
} // namespace HistoryView

View file

@ -219,10 +219,14 @@ QSize Photo::countCurrentSize(int newWidth) {
: st::minPhotoSize),
thumbMaxWidth);
const auto dimensions = photoSize();
auto pix = CountPhotoMediaSize(
CountDesiredMediaSize(dimensions),
newWidth,
maxWidth());
auto pix = _data->extendedMediaVideoDuration()
? CountMediaSize(
CountDesiredMediaSize(dimensions),
newWidth)
: CountPhotoMediaSize(
CountDesiredMediaSize(dimensions),
newWidth,
maxWidth());
newWidth = qMax(pix.width(), minWidth);
auto newHeight = qMax(pix.height(), st::minPhotoSize);
if (_parent->hasBubble()) {

View file

@ -1362,7 +1362,7 @@ void OverlayWidget::updateControls() {
if (_message) {
return ItemDateTime(_message);
} else if (_photo) {
return base::unixtime::parse(_photo->date);
return base::unixtime::parse(_photo->date());
} else if (_document) {
return base::unixtime::parse(_document->date);
}
@ -2436,7 +2436,7 @@ void OverlayWidget::saveAs() {
u".mp4"_q,
QString(),
false,
_photo->date),
_photo->date()),
crl::guard(_window, [=](const QString &result) {
QFile f(result);
if (!result.isEmpty()
@ -2467,7 +2467,7 @@ void OverlayWidget::saveAs() {
u".jpg"_q,
QString(),
false,
_photo->date),
_photo->date()),
crl::guard(_window, [=](const QString &result) {
if (!result.isEmpty() && _photo == photo) {
media->saveToFile(result);

View file

@ -44,6 +44,7 @@ inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> s
inputMediaDice#e66fbf7b emoticon:string = InputMedia;
inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia;
inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia;
inputMediaPaidMedia#aa661fc3 stars_amount:long extended_media:Vector<InputMedia> = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto;
@ -133,6 +134,7 @@ messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia;
messageMediaGiveaway#daad85b0 flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector<long> countries_iso2:flags.1?Vector<string> prize_description:flags.3?string quantity:int months:int until_date:int = MessageMedia;
messageMediaGiveawayResults#c6991068 flags:# only_new_subscribers:flags.0?true refunded:flags.2?true channel_id:long additional_peers_count:flags.3?int launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector<long> months:int prize_description:flags.1?string until_date:int = MessageMedia;
messageMediaPaidMedia#a8852491 stars_amount:long extended_media:Vector<MessageExtendedMedia> = MessageMedia;
messageActionEmpty#b6aef7b0 = MessageAction;
messageActionChatCreate#bd47cbad title:string users:Vector<long> = MessageAction;
@ -384,7 +386,7 @@ updateUserEmojiStatus#28373599 user_id:long emoji_status:EmojiStatus = Update;
updateRecentEmojiStatuses#30f443db = Update;
updateRecentReactions#6f7863f4 = Update;
updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update;
updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update;
updateMessageExtendedMedia#d5a41724 peer:Peer msg_id:int extended_media:Vector<MessageExtendedMedia> = Update;
updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long topic_id:int = Update;
updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector<int> = Update;
updateUser#20529438 user_id:long = Update;
@ -1806,7 +1808,7 @@ starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer;
starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption;
starsTransaction#aa00c898 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string = StarsTransaction;
starsTransaction#2db5418f flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> = StarsTransaction;
payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector<StarsTransaction> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = payments.StarsStatus;
@ -2488,4 +2490,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool;
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
// LAYER 182
// LAYER 183

View file

@ -968,7 +968,7 @@ CopyRestrictionType ShortcutMessages::listCopyMediaRestrictionType(
not_null<HistoryItem*> item) {
if (const auto media = item->media()) {
if (const auto invoice = media->invoice()) {
if (invoice->extendedMedia) {
if (!invoice->extendedMedia.empty()) {
return CopyMediaRestrictionTypeFor(_history->peer, item);
}
}

View file

@ -416,7 +416,7 @@ void UserpicButton::openPeerPhoto() {
return;
}
const auto photo = _peer->owner().photo(id);
if (photo->date && _controller) {
if (photo->date() && _controller) {
_controller->openPhoto(photo, _peer);
}
}
@ -744,7 +744,7 @@ void UserpicButton::updateVideo() {
return;
}
const auto photo = _peer->owner().photo(id);
if (!photo->date || !photo->videoCanBePlayed()) {
if (!photo->date() || !photo->videoCanBePlayed()) {
clearStreaming();
return;
} else if (_streamed && _streamedPhoto == photo) {