Show correct chat preview for paid media.

This commit is contained in:
John Preston 2024-06-19 15:01:28 +04:00
parent 5f8da27c86
commit 950a946a16
9 changed files with 184 additions and 54 deletions

View file

@ -4214,11 +4214,18 @@ void ApiWrap::sendMediaWithRandomId(
void ApiWrap::sendMultiPaidMedia( void ApiWrap::sendMultiPaidMedia(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const QVector<MTPInputMedia> &medias, not_null<SendingAlbum*> album,
Api::SendOptions options,
uint64 randomId,
Fn<void(bool)> done) { Fn<void(bool)> done) {
Expects(options.price > 0); Expects(album->options.price > 0);
const auto groupId = album->groupId;
const auto &options = album->options;
const auto randomId = album->items.front().randomId;
auto medias = album->items | ranges::view::transform([](
const SendingAlbum::Item &part) {
Assert(part.media.has_value());
return MTPInputMedia(part.media->data().vmedia());
}) | ranges::to<QVector<MTPInputMedia>>();
const auto history = item->history(); const auto history = item->history();
const auto replyTo = item->replyTo(); const auto replyTo = item->replyTo();
@ -4256,7 +4263,7 @@ void ApiWrap::sendMultiPaidMedia(
Data::Histories::ReplyToPlaceholder(), Data::Histories::ReplyToPlaceholder(),
MTP_inputMediaPaidMedia( MTP_inputMediaPaidMedia(
MTP_long(options.price), MTP_long(options.price),
MTP_vector<MTPInputMedia>(medias)), MTP_vector<MTPInputMedia>(std::move(medias))),
MTP_string(caption.text), MTP_string(caption.text),
MTP_long(randomId), MTP_long(randomId),
MTPReplyMarkup(), MTPReplyMarkup(),
@ -4266,6 +4273,14 @@ void ApiWrap::sendMultiPaidMedia(
Data::ShortcutIdToMTP(_session, options.shortcutId), Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId) MTP_long(options.effectId)
), [=](const MTPUpdates &result, const MTP::Response &response) { ), [=](const MTPUpdates &result, const MTP::Response &response) {
if (const auto album = _sendingAlbums.take(groupId)) {
const auto copy = (*album)->items;
for (const auto &part : copy) {
if (const auto item = history->owner().message(part.msgId)) {
item->destroy();
}
}
}
if (done) done(true); if (done) done(true);
}, [=](const MTP::Error &error, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) {
if (done) done(false); if (done) done(false);
@ -4326,6 +4341,9 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
if (!sample) { if (!sample) {
_sendingAlbums.remove(groupId); _sendingAlbums.remove(groupId);
return; return;
} else if (album->options.price > 0) {
sendMultiPaidMedia(sample, album);
return;
} else if (medias.size() < 2) { } else if (medias.size() < 2) {
const auto &single = medias.front().data(); const auto &single = medias.front().data();
sendMediaWithRandomId( sendMediaWithRandomId(
@ -4335,19 +4353,6 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
single.vrandom_id().v); single.vrandom_id().v);
_sendingAlbums.remove(groupId); _sendingAlbums.remove(groupId);
return; 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 history = sample->history();
const auto replyTo = sample->replyTo(); const auto replyTo = sample->replyTo();

View file

@ -547,9 +547,7 @@ private:
Fn<void(bool)> done = nullptr); Fn<void(bool)> done = nullptr);
void sendMultiPaidMedia( void sendMultiPaidMedia(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const QVector<MTPInputMedia> &medias, not_null<SendingAlbum*> album,
Api::SendOptions options,
uint64 randomId,
Fn<void(bool)> done = nullptr); Fn<void(bool)> done = nullptr);
void getTopPromotionDelayed(TimeId now, TimeId next); void getTopPromotionDelayed(TimeId now, TimeId next);

View file

@ -81,6 +81,7 @@ void Groups::refreshMessage(
bool justRefreshViews) { bool justRefreshViews) {
if (!isGrouped(item)) { if (!isGrouped(item)) {
unregisterMessage(item); unregisterMessage(item);
_data->requestItemViewRefresh(item);
return; return;
} }
if (!item->isRegular() && !item->isScheduled()) { if (!item->isRegular() && !item->isScheduled()) {

View file

@ -85,6 +85,13 @@ constexpr auto kLoadingStoryPhotoId = PhotoId(0x7FFF'DEAD'FFFF'FFFFULL);
using ItemPreview = HistoryView::ItemPreview; using ItemPreview = HistoryView::ItemPreview;
using ItemPreviewImage = HistoryView::ItemPreviewImage; using ItemPreviewImage = HistoryView::ItemPreviewImage;
struct AlbumCounts {
int photos = 0;
int videos = 0;
int audios = 0;
int files = 0;
};
[[nodiscard]] TextWithEntities WithCaptionNotificationText( [[nodiscard]] TextWithEntities WithCaptionNotificationText(
const QString &attachType, const QString &attachType,
const TextWithEntities &caption, const TextWithEntities &caption,
@ -166,7 +173,7 @@ template <typename MediaType>
return (reinterpret_cast<uint64>(data.get()) & ~1) | (spoiler ? 1 : 0); return (reinterpret_cast<uint64>(data.get()) & ~1) | (spoiler ? 1 : 0);
} }
[[nodiscard]] ItemPreviewImage PreparePhotoPreview( [[nodiscard]] ItemPreviewImage PreparePhotoPreviewImage(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
const std::shared_ptr<PhotoMedia> &media, const std::shared_ptr<PhotoMedia> &media,
ImageRoundRadius radius, ImageRoundRadius radius,
@ -182,14 +189,15 @@ template <typename MediaType>
} }
const auto allowedToDownload = media->autoLoadThumbnailAllowed( const auto allowedToDownload = media->autoLoadThumbnailAllowed(
item->history()->peer); item->history()->peer);
const auto cacheKey = allowedToDownload ? 0 : counted; const auto spoilered = uint64(spoiler ? 1 : 0);
const auto cacheKey = allowedToDownload ? spoilered : counted;
if (allowedToDownload) { if (allowedToDownload) {
media->owner()->load(PhotoSize::Small, item->fullId()); media->owner()->load(PhotoSize::Small, item->fullId());
} }
if (const auto blurred = media->thumbnailInline()) { if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius, spoiler), cacheKey }; return { PreparePreviewImage(blurred, radius, spoiler), cacheKey };
} }
return { QImage(), allowedToDownload ? 0 : cacheKey }; return { QImage(), allowedToDownload ? spoilered : cacheKey };
} }
[[nodiscard]] ItemPreviewImage PrepareFilePreviewImage( [[nodiscard]] ItemPreviewImage PrepareFilePreviewImage(
@ -208,10 +216,11 @@ template <typename MediaType>
}; };
} }
document->loadThumbnail(item->fullId()); document->loadThumbnail(item->fullId());
const auto spoilered = uint64(spoiler ? 1 : 0);
if (const auto blurred = media->thumbnailInline()) { if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius, spoiler), 0 }; return { PreparePreviewImage(blurred, radius, spoiler), spoilered };
} }
return { QImage(), 0 }; return { QImage(), spoilered };
} }
[[nodiscard]] QImage PutPlayIcon(QImage preview) { [[nodiscard]] QImage PutPlayIcon(QImage preview) {
@ -226,6 +235,18 @@ template <typename MediaType>
return preview; return preview;
} }
[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
not_null<const HistoryItem*> item,
const std::shared_ptr<PhotoMedia> &media,
ImageRoundRadius radius,
bool spoiler) {
auto result = PreparePhotoPreviewImage(item, media, radius, spoiler);
if (media->owner()->extendedMediaVideoDuration().has_value()) {
result.data = PutPlayIcon(std::move(result.data));
}
return result;
}
[[nodiscard]] ItemPreviewImage PrepareFilePreview( [[nodiscard]] ItemPreviewImage PrepareFilePreview(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
const std::shared_ptr<DocumentMedia> &media, const std::shared_ptr<DocumentMedia> &media,
@ -280,7 +301,7 @@ bool UpdateExtendedMedia(
auto changed = false; auto changed = false;
auto size = QSize(); auto size = QSize();
auto thumbnail = QByteArray(); auto thumbnail = QByteArray();
auto videoDuration = TimeId(); auto videoDuration = std::optional<TimeId>();
if (const auto &w = data.vw()) { if (const auto &w = data.vw()) {
const auto &h = data.vh(); const auto &h = data.vh();
Assert(h.has_value()); Assert(h.has_value());
@ -302,6 +323,8 @@ bool UpdateExtendedMedia(
if (photo->extendedMediaVideoDuration() != videoDuration) { if (photo->extendedMediaVideoDuration() != videoDuration) {
changed = true; changed = true;
} }
} else if (photo->extendedMediaVideoDuration().has_value()) {
changed = true;
} }
if (changed) { if (changed) {
photo->setExtendedMediaPreview(size, thumbnail, videoDuration); photo->setExtendedMediaPreview(size, thumbnail, videoDuration);
@ -355,6 +378,29 @@ TextForMimeData WithCaptionClipboardText(
return result; return result;
} }
[[nodiscard]] QString ComputeAlbumCountsString(AlbumCounts counts) {
const auto medias = counts.photos + counts.videos;
return (counts.photos && counts.videos)
? tr::lng_in_dlg_media_count(tr::now, lt_count, medias)
: (counts.photos > 1)
? tr::lng_in_dlg_photo_count(tr::now, lt_count, counts.photos)
: counts.photos
? tr::lng_in_dlg_photo(tr::now)
: (counts.videos > 1)
? tr::lng_in_dlg_video_count(tr::now, lt_count, counts.videos)
: counts.videos
? tr::lng_in_dlg_video(tr::now)
: (counts.audios > 1)
? tr::lng_in_dlg_audio_count(tr::now, lt_count, counts.audios)
: counts.audios
? tr::lng_in_dlg_audio(tr::now)
: (counts.files > 1)
? tr::lng_in_dlg_file_count(tr::now, lt_count, counts.files)
: counts.files
? tr::lng_in_dlg_file(tr::now)
: tr::lng_in_dlg_album(tr::now);
}
} // namespace } // namespace
Invoice ComputeInvoiceData( Invoice ComputeInvoiceData(
@ -481,6 +527,15 @@ bool HasUnpaidMedia(const Invoice &invoice) {
return false; return false;
} }
bool IsFirstVideo(const Invoice &invoice) {
if (invoice.extendedMedia.empty()) {
return false;
} else if (const auto photo = invoice.extendedMedia.front()->photo()) {
return photo->extendedMediaVideoDuration().has_value();
}
return true;
}
Media::Media(not_null<HistoryItem*> parent) : _parent(parent) { Media::Media(not_null<HistoryItem*> parent) : _parent(parent) {
} }
@ -643,21 +698,18 @@ ItemPreview Media::toGroupPreview(
ToPreviewOptions options) const { ToPreviewOptions options) const {
auto result = ItemPreview(); auto result = ItemPreview();
auto loadingContext = std::vector<std::any>(); auto loadingContext = std::vector<std::any>();
auto photoCount = 0; auto counts = AlbumCounts();
auto videoCount = 0;
auto audioCount = 0;
auto fileCount = 0;
auto manyCaptions = false; auto manyCaptions = false;
for (const auto &item : items) { for (const auto &item : items) {
if (const auto media = item->media()) { if (const auto media = item->media()) {
if (media->photo()) { if (media->photo()) {
photoCount++; counts.photos++;
} else if (const auto document = media->document()) { } else if (const auto document = media->document()) {
(document->isVideoFile() (document->isVideoFile()
? videoCount ? counts.videos
: document->isAudioFile() : document->isAudioFile()
? audioCount ? counts.audios
: fileCount)++; : counts.files)++;
} }
auto copy = options; auto copy = options;
copy.ignoreGroup = true; copy.ignoreGroup = true;
@ -687,19 +739,7 @@ ItemPreview Media::toGroupPreview(
} }
} }
if (manyCaptions || result.text.text.isEmpty()) { if (manyCaptions || result.text.text.isEmpty()) {
const auto mediaCount = photoCount + videoCount; result.text = Ui::Text::Colorized(ComputeAlbumCountsString(counts));
auto genericText = (photoCount && videoCount)
? tr::lng_in_dlg_media_count(tr::now, lt_count, mediaCount)
: photoCount
? tr::lng_in_dlg_photo_count(tr::now, lt_count, photoCount)
: videoCount
? tr::lng_in_dlg_video_count(tr::now, lt_count, videoCount)
: audioCount
? tr::lng_in_dlg_audio_count(tr::now, lt_count, audioCount)
: fileCount
? tr::lng_in_dlg_file_count(tr::now, lt_count, fileCount)
: tr::lng_in_dlg_album(tr::now);
result.text = Ui::Text::Colorized(genericText);
} }
if (!loadingContext.empty()) { if (!loadingContext.empty()) {
result.loadingContext = std::move(loadingContext); result.loadingContext = std::move(loadingContext);
@ -1952,9 +1992,87 @@ bool MediaInvoice::replyPreviewLoaded() const {
} }
TextWithEntities MediaInvoice::notificationText() const { TextWithEntities MediaInvoice::notificationText() const {
if (_invoice.isPaidMedia && !_invoice.extendedMedia.empty()) {
return WithCaptionNotificationText(
(IsFirstVideo(_invoice)
? tr::lng_in_dlg_video
: tr::lng_in_dlg_photo)(tr::now),
parent()->originalText());
}
return { .text = _invoice.title }; return { .text = _invoice.title };
} }
ItemPreview MediaInvoice::toPreview(ToPreviewOptions options) const {
if (!_invoice.isPaidMedia || _invoice.extendedMedia.empty()) {
return Media::toPreview(options);
}
auto counts = AlbumCounts();
const auto item = parent();
auto images = std::vector<ItemPreviewImage>();
auto context = std::vector<std::any>();
const auto existing = options.existing;
const auto spoiler = HasUnpaidMedia(_invoice);
for (const auto &media : _invoice.extendedMedia) {
const auto raw = media.get();
const auto photo = raw->photo();
const auto document = raw->document();
if (!photo && !document) {
continue;
} else if (images.size() < kMaxPreviewImages) {
auto found = photo
? FindCachedPreview(existing, not_null(photo), spoiler)
: FindCachedPreview(existing, not_null(document), spoiler);
const auto radius = ImageRoundRadius::Small;
if (found) {
images.push_back(std::move(found));
} else if (photo) {
const auto media = photo->createMediaView();
if (auto prepared = PreparePhotoPreview(
parent(),
media,
radius,
spoiler)
; prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context.push_back(media);
}
}
} else if (TryFilePreview(document)) {
const auto media = document->createMediaView();
if (auto prepared = PrepareFilePreview(
parent(),
media,
radius,
spoiler)
; prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context.push_back(media);
}
}
}
}
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
++counts.photos;
} else {
++counts.videos;
}
}
const auto type = ComputeAlbumCountsString(counts);
const auto caption = (options.hideCaption || options.ignoreMessageText)
? TextWithEntities()
: options.translated
? parent()->translatedText()
: parent()->originalText();
const auto hasMiniImages = !images.empty();
return {
.text = WithCaptionNotificationText(type, caption, hasMiniImages),
.images = std::move(images),
.loadingContext = std::move(context),
};
}
QString MediaInvoice::pinnedTextSubstring() const { QString MediaInvoice::pinnedTextSubstring() const {
return QString::fromUtf8("\xC2\xAB") return QString::fromUtf8("\xC2\xAB")
+ _invoice.title + _invoice.title

View file

@ -99,6 +99,7 @@ struct Invoice {
}; };
[[nodiscard]] bool HasExtendedMedia(const Invoice &invoice); [[nodiscard]] bool HasExtendedMedia(const Invoice &invoice);
[[nodiscard]] bool HasUnpaidMedia(const Invoice &invoice); [[nodiscard]] bool HasUnpaidMedia(const Invoice &invoice);
[[nodiscard]] bool IsFirstVideo(const Invoice &invoice);
struct GiveawayStart { struct GiveawayStart {
std::vector<not_null<ChannelData*>> channels; std::vector<not_null<ChannelData*>> channels;
@ -506,6 +507,7 @@ public:
Image *replyPreview() const override; Image *replyPreview() const override;
bool replyPreviewLoaded() const override; bool replyPreviewLoaded() const override;
TextWithEntities notificationText() const override; TextWithEntities notificationText() const override;
ItemPreview toPreview(ToPreviewOptions way) const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override; TextForMimeData clipboardText() const override;

View file

@ -59,7 +59,7 @@ void PhotoData::setFields(TimeId date, bool hasAttachedStickers) {
void PhotoData::setExtendedMediaPreview( void PhotoData::setExtendedMediaPreview(
QSize dimensions, QSize dimensions,
const QByteArray &inlineThumbnailBytes, const QByteArray &inlineThumbnailBytes,
TimeId videoDuration) { std::optional<TimeId> videoDuration) {
_extendedMediaPreview = true; _extendedMediaPreview = true;
updateImages( updateImages(
inlineThumbnailBytes, inlineThumbnailBytes,
@ -69,7 +69,7 @@ void PhotoData::setExtendedMediaPreview(
{}, {},
{}, {},
{}); {});
_dateOrExtendedVideoDuration = videoDuration + 1; _dateOrExtendedVideoDuration = videoDuration ? (*videoDuration + 1) : 0;
} }
bool PhotoData::extendedMediaPreview() const { bool PhotoData::extendedMediaPreview() const {

View file

@ -94,7 +94,7 @@ public:
void setExtendedMediaPreview( void setExtendedMediaPreview(
QSize dimensions, QSize dimensions,
const QByteArray &inlineThumbnailBytes, const QByteArray &inlineThumbnailBytes,
TimeId videoDuration); std::optional<TimeId> videoDuration);
[[nodiscard]] bool extendedMediaPreview() const; [[nodiscard]] bool extendedMediaPreview() const;
[[nodiscard]] std::optional<TimeId> extendedMediaVideoDuration() const; [[nodiscard]] std::optional<TimeId> extendedMediaVideoDuration() const;

View file

@ -104,7 +104,11 @@ HistoryItem *GroupedMedia::itemForText() const {
auto result = (HistoryItem*)nullptr; auto result = (HistoryItem*)nullptr;
for (const auto &part : _parts) { for (const auto &part : _parts) {
if (!part.item->emptyText()) { if (!part.item->emptyText()) {
if (result) { if (result == part.item) {
// All parts are from the same message, that means
// this is an album with a single item, single text.
return result;
} else if (result) {
return nullptr; return nullptr;
} else { } else {
result = part.item; result = part.item;

View file

@ -618,7 +618,8 @@ FillMenuResult FillSendMenu(
const auto sending = (type != Type::Disabled); const auto sending = (type != Type::Disabled);
const auto empty = !sending const auto empty = !sending
&& (details.spoiler == SpoilerState::None) && (details.spoiler == SpoilerState::None)
&& (details.caption == CaptionState::None); && (details.caption == CaptionState::None)
&& !details.price.has_value();
if (empty || !action) { if (empty || !action) {
return FillMenuResult::Skipped; return FillMenuResult::Skipped;
} }
@ -651,7 +652,8 @@ FillMenuResult FillSendMenu(
if ((type != Type::Disabled) if ((type != Type::Disabled)
&& ((details.spoiler != SpoilerState::None) && ((details.spoiler != SpoilerState::None)
|| (details.caption != CaptionState::None))) { || (details.caption != CaptionState::None)
|| details.price.has_value())) {
menu->addSeparator(&st::expandedMenuSeparator); menu->addSeparator(&st::expandedMenuSeparator);
} }
if (details.spoiler != SpoilerState::None) { if (details.spoiler != SpoilerState::None) {