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(
not_null<HistoryItem*> item,
const QVector<MTPInputMedia> &medias,
Api::SendOptions options,
uint64 randomId,
not_null<SendingAlbum*> album,
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 replyTo = item->replyTo();
@ -4256,7 +4263,7 @@ void ApiWrap::sendMultiPaidMedia(
Data::Histories::ReplyToPlaceholder(),
MTP_inputMediaPaidMedia(
MTP_long(options.price),
MTP_vector<MTPInputMedia>(medias)),
MTP_vector<MTPInputMedia>(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<SendingAlbum*> 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<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

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

View file

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

View file

@ -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 <typename MediaType>
return (reinterpret_cast<uint64>(data.get()) & ~1) | (spoiler ? 1 : 0);
}
[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
[[nodiscard]] ItemPreviewImage PreparePhotoPreviewImage(
not_null<const HistoryItem*> item,
const std::shared_ptr<PhotoMedia> &media,
ImageRoundRadius radius,
@ -182,14 +189,15 @@ template <typename MediaType>
}
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 <typename MediaType>
};
}
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 <typename MediaType>
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(
not_null<const HistoryItem*> item,
const std::shared_ptr<DocumentMedia> &media,
@ -280,7 +301,7 @@ bool UpdateExtendedMedia(
auto changed = false;
auto size = QSize();
auto thumbnail = QByteArray();
auto videoDuration = TimeId();
auto videoDuration = std::optional<TimeId>();
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<HistoryItem*> parent) : _parent(parent) {
}
@ -643,21 +698,18 @@ ItemPreview Media::toGroupPreview(
ToPreviewOptions options) const {
auto result = ItemPreview();
auto loadingContext = std::vector<std::any>();
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<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 {
return QString::fromUtf8("\xC2\xAB")
+ _invoice.title

View file

@ -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<not_null<ChannelData*>> 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;

View file

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

View file

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

View file

@ -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;

View file

@ -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) {