From 950a946a1650c3cc850033723bd3d621308d94c4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 19 Jun 2024 15:01:28 +0400 Subject: [PATCH] Show correct chat preview for paid media. --- Telegram/SourceFiles/apiwrap.cpp | 41 +++-- Telegram/SourceFiles/apiwrap.h | 4 +- Telegram/SourceFiles/data/data_groups.cpp | 1 + .../SourceFiles/data/data_media_types.cpp | 172 +++++++++++++++--- Telegram/SourceFiles/data/data_media_types.h | 2 + Telegram/SourceFiles/data/data_photo.cpp | 4 +- Telegram/SourceFiles/data/data_photo.h | 2 +- .../view/media/history_view_media_grouped.cpp | 6 +- Telegram/SourceFiles/menu/menu_send.cpp | 6 +- 9 files changed, 184 insertions(+), 54 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 86481cec7..04a2e25f1 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4214,11 +4214,18 @@ void ApiWrap::sendMediaWithRandomId( void ApiWrap::sendMultiPaidMedia( not_null item, - const QVector &medias, - Api::SendOptions options, - uint64 randomId, + not_null album, Fn 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>(); const auto history = item->history(); const auto replyTo = item->replyTo(); @@ -4256,7 +4263,7 @@ void ApiWrap::sendMultiPaidMedia( Data::Histories::ReplyToPlaceholder(), MTP_inputMediaPaidMedia( MTP_long(options.price), - MTP_vector(medias)), + MTP_vector(std::move(medias))), MTP_string(caption.text), MTP_long(randomId), MTPReplyMarkup(), @@ -4266,6 +4273,14 @@ void ApiWrap::sendMultiPaidMedia( Data::ShortcutIdToMTP(_session, options.shortcutId), MTP_long(options.effectId) ), [=](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); }, [=](const MTP::Error &error, const MTP::Response &response) { if (done) done(false); @@ -4326,6 +4341,9 @@ void ApiWrap::sendAlbumIfReady(not_null album) { if (!sample) { _sendingAlbums.remove(groupId); return; + } else if (album->options.price > 0) { + sendMultiPaidMedia(sample, album); + return; } else if (medias.size() < 2) { const auto &single = medias.front().data(); sendMediaWithRandomId( @@ -4335,19 +4353,6 @@ void ApiWrap::sendAlbumIfReady(not_null 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>(); - 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(); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 3904eb3d3..15c0941c3 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -547,9 +547,7 @@ private: Fn done = nullptr); void sendMultiPaidMedia( not_null item, - const QVector &medias, - Api::SendOptions options, - uint64 randomId, + not_null album, Fn done = nullptr); void getTopPromotionDelayed(TimeId now, TimeId next); diff --git a/Telegram/SourceFiles/data/data_groups.cpp b/Telegram/SourceFiles/data/data_groups.cpp index cf75482db..15bb0d820 100644 --- a/Telegram/SourceFiles/data/data_groups.cpp +++ b/Telegram/SourceFiles/data/data_groups.cpp @@ -81,6 +81,7 @@ void Groups::refreshMessage( bool justRefreshViews) { if (!isGrouped(item)) { unregisterMessage(item); + _data->requestItemViewRefresh(item); return; } if (!item->isRegular() && !item->isScheduled()) { diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 1a8b052db..9ccb6ea4e 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -85,6 +85,13 @@ constexpr auto kLoadingStoryPhotoId = PhotoId(0x7FFF'DEAD'FFFF'FFFFULL); using ItemPreview = HistoryView::ItemPreview; using ItemPreviewImage = HistoryView::ItemPreviewImage; +struct AlbumCounts { + int photos = 0; + int videos = 0; + int audios = 0; + int files = 0; +}; + [[nodiscard]] TextWithEntities WithCaptionNotificationText( const QString &attachType, const TextWithEntities &caption, @@ -166,7 +173,7 @@ template return (reinterpret_cast(data.get()) & ~1) | (spoiler ? 1 : 0); } -[[nodiscard]] ItemPreviewImage PreparePhotoPreview( +[[nodiscard]] ItemPreviewImage PreparePhotoPreviewImage( not_null item, const std::shared_ptr &media, ImageRoundRadius radius, @@ -182,14 +189,15 @@ template } const auto allowedToDownload = media->autoLoadThumbnailAllowed( item->history()->peer); - const auto cacheKey = allowedToDownload ? 0 : counted; + const auto spoilered = uint64(spoiler ? 1 : 0); + const auto cacheKey = allowedToDownload ? spoilered : counted; if (allowedToDownload) { media->owner()->load(PhotoSize::Small, item->fullId()); } if (const auto blurred = media->thumbnailInline()) { return { PreparePreviewImage(blurred, radius, spoiler), cacheKey }; } - return { QImage(), allowedToDownload ? 0 : cacheKey }; + return { QImage(), allowedToDownload ? spoilered : cacheKey }; } [[nodiscard]] ItemPreviewImage PrepareFilePreviewImage( @@ -208,10 +216,11 @@ template }; } document->loadThumbnail(item->fullId()); + const auto spoilered = uint64(spoiler ? 1 : 0); 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) { @@ -226,6 +235,18 @@ template return preview; } +[[nodiscard]] ItemPreviewImage PreparePhotoPreview( + not_null item, + const std::shared_ptr &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( not_null item, const std::shared_ptr &media, @@ -280,7 +301,7 @@ bool UpdateExtendedMedia( auto changed = false; auto size = QSize(); auto thumbnail = QByteArray(); - auto videoDuration = TimeId(); + auto videoDuration = std::optional(); if (const auto &w = data.vw()) { const auto &h = data.vh(); Assert(h.has_value()); @@ -302,6 +323,8 @@ bool UpdateExtendedMedia( if (photo->extendedMediaVideoDuration() != videoDuration) { changed = true; } + } else if (photo->extendedMediaVideoDuration().has_value()) { + changed = true; } if (changed) { photo->setExtendedMediaPreview(size, thumbnail, videoDuration); @@ -355,6 +378,29 @@ TextForMimeData WithCaptionClipboardText( 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 Invoice ComputeInvoiceData( @@ -481,6 +527,15 @@ bool HasUnpaidMedia(const Invoice &invoice) { 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 parent) : _parent(parent) { } @@ -643,21 +698,18 @@ ItemPreview Media::toGroupPreview( ToPreviewOptions options) const { auto result = ItemPreview(); auto loadingContext = std::vector(); - auto photoCount = 0; - auto videoCount = 0; - auto audioCount = 0; - auto fileCount = 0; + auto counts = AlbumCounts(); auto manyCaptions = false; for (const auto &item : items) { if (const auto media = item->media()) { if (media->photo()) { - photoCount++; + counts.photos++; } else if (const auto document = media->document()) { (document->isVideoFile() - ? videoCount + ? counts.videos : document->isAudioFile() - ? audioCount - : fileCount)++; + ? counts.audios + : counts.files)++; } auto copy = options; copy.ignoreGroup = true; @@ -687,19 +739,7 @@ ItemPreview Media::toGroupPreview( } } if (manyCaptions || result.text.text.isEmpty()) { - const auto mediaCount = photoCount + videoCount; - 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); + result.text = Ui::Text::Colorized(ComputeAlbumCountsString(counts)); } if (!loadingContext.empty()) { result.loadingContext = std::move(loadingContext); @@ -1952,9 +1992,87 @@ bool MediaInvoice::replyPreviewLoaded() 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 }; } +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(); + auto context = std::vector(); + 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 { return QString::fromUtf8("\xC2\xAB") + _invoice.title diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 62d03c53d..424e2c448 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -99,6 +99,7 @@ struct Invoice { }; [[nodiscard]] bool HasExtendedMedia(const Invoice &invoice); [[nodiscard]] bool HasUnpaidMedia(const Invoice &invoice); +[[nodiscard]] bool IsFirstVideo(const Invoice &invoice); struct GiveawayStart { std::vector> channels; @@ -506,6 +507,7 @@ public: Image *replyPreview() const override; bool replyPreviewLoaded() const override; TextWithEntities notificationText() const override; + ItemPreview toPreview(ToPreviewOptions way) const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index 35186750b..869bbf733 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -59,7 +59,7 @@ void PhotoData::setFields(TimeId date, bool hasAttachedStickers) { void PhotoData::setExtendedMediaPreview( QSize dimensions, const QByteArray &inlineThumbnailBytes, - TimeId videoDuration) { + std::optional videoDuration) { _extendedMediaPreview = true; updateImages( inlineThumbnailBytes, @@ -69,7 +69,7 @@ void PhotoData::setExtendedMediaPreview( {}, {}, {}); - _dateOrExtendedVideoDuration = videoDuration + 1; + _dateOrExtendedVideoDuration = videoDuration ? (*videoDuration + 1) : 0; } bool PhotoData::extendedMediaPreview() const { diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index 98166d265..3cd749a71 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -94,7 +94,7 @@ public: void setExtendedMediaPreview( QSize dimensions, const QByteArray &inlineThumbnailBytes, - TimeId videoDuration); + std::optional videoDuration); [[nodiscard]] bool extendedMediaPreview() const; [[nodiscard]] std::optional extendedMediaVideoDuration() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 9f93e7203..16d9d8080 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -104,7 +104,11 @@ HistoryItem *GroupedMedia::itemForText() const { auto result = (HistoryItem*)nullptr; for (const auto &part : _parts) { 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; } else { result = part.item; diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 2ebfba197..fb03be230 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -618,7 +618,8 @@ FillMenuResult FillSendMenu( const auto sending = (type != Type::Disabled); const auto empty = !sending && (details.spoiler == SpoilerState::None) - && (details.caption == CaptionState::None); + && (details.caption == CaptionState::None) + && !details.price.has_value(); if (empty || !action) { return FillMenuResult::Skipped; } @@ -651,7 +652,8 @@ FillMenuResult FillSendMenu( if ((type != Type::Disabled) && ((details.spoiler != SpoilerState::None) - || (details.caption != CaptionState::None))) { + || (details.caption != CaptionState::None) + || details.price.has_value())) { menu->addSeparator(&st::expandedMenuSeparator); } if (details.spoiler != SpoilerState::None) {