From 4c5c2aadc4e80164c28f2455f280e937f8670df7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 15 Nov 2023 18:18:18 +0300 Subject: [PATCH] Replaced style of sponsored messages with fake webpage. --- Telegram/Resources/langs/lang.strings | 4 +- .../data/data_sponsored_messages.cpp | 36 ++---- .../data/data_sponsored_messages.h | 4 +- Telegram/SourceFiles/data/data_types.h | 1 + Telegram/SourceFiles/history/history_item.cpp | 43 +++++-- .../history/view/history_view_bottom_info.cpp | 21 ++-- .../history/view/history_view_bottom_info.h | 1 - .../history/view/history_view_message.cpp | 18 +-- .../view/media/history_view_web_page.cpp | 117 ++++++++++++++---- .../view/media/history_view_web_page.h | 11 +- 10 files changed, 168 insertions(+), 88 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 514a3d929..24b675f10 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1823,8 +1823,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_forwarded_hidden" = "The account was hidden by the user."; "lng_forwarded_imported" = "This message was imported from another app. It may not be real."; "lng_signed_author" = "Author: {user}"; -"lng_sponsored" = "sponsored"; -"lng_recommended" = "recommended"; +"lng_sponsored_message_title" = "Sponsored"; +"lng_recommended_message_title" = "Recommended"; "lng_edited" = "edited"; "lng_edited_date" = "Edited: {date}"; "lng_sent_date" = "Sent: {date}"; diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.cpp b/Telegram/SourceFiles/data/data_sponsored_messages.cpp index eb61e2431..041448d46 100644 --- a/Telegram/SourceFiles/data/data_sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/data_sponsored_messages.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "data/data_channel.h" #include "data/data_peer_id.h" +#include "data/data_photo.h" #include "data/data_session.h" #include "data/data_user.h" #include "history/history.h" @@ -275,39 +276,22 @@ void SponsoredMessages::append( .isBot = (peer->isUser() && peer->asUser()->isBot()), .isExactPost = exactPost, .isRecommended = data.is_recommended(), - .userpic = { .location = peer->userpicLocation() }, .isForceUserpicDisplay = data.is_show_peer_photo(), }; }; const auto externalLink = data.vwebpage() ? qs(data.vwebpage()->data().vurl()) : QString(); - const auto userpicFromPhoto = [&](const MTPphoto &photo) { - return photo.match([&](const MTPDphoto &data) { - for (const auto &size : data.vsizes().v) { - const auto result = Images::FromPhotoSize( - _session, - data, - size); - if (result.location.valid()) { - return result; - } - } - return ImageWithLocation{}; - }, [](const MTPDphotoEmpty &) { - return ImageWithLocation{}; - }); - }; const auto from = [&]() -> SponsoredFrom { if (const auto webpage = data.vwebpage()) { const auto &data = webpage->data(); - auto userpic = data.vphoto() - ? userpicFromPhoto(*data.vphoto()) - : ImageWithLocation{}; + const auto photoId = data.vphoto() + ? _session->data().processPhoto(*data.vphoto())->id + : PhotoId(0); return SponsoredFrom{ .title = qs(data.vsite_name()), .externalLink = externalLink, - .userpic = std::move(userpic), + .externalLinkPhotoId = photoId, .isForceUserpicDisplay = message.data().is_show_peer_photo(), }; } else if (const auto fromId = data.vfrom_id()) { @@ -317,14 +301,12 @@ void SponsoredMessages::append( } Assert(data.vchat_invite()); return data.vchat_invite()->match([&](const MTPDchatInvite &data) { - auto userpic = userpicFromPhoto(data.vphoto()); return SponsoredFrom{ .title = qs(data.vtitle()), .isBroadcast = data.is_broadcast(), .isMegagroup = data.is_megagroup(), .isChannel = data.is_channel(), .isPublic = data.is_public(), - .userpic = std::move(userpic), .isForceUserpicDisplay = message.data().is_show_peer_photo(), }; }, [&](const MTPDchatInviteAlready &data) { @@ -437,7 +419,7 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails( const auto &hash = data.chatInviteHash; using InfoList = std::vector; - const auto info = (!data.sponsorInfo.text.isEmpty() + auto info = (!data.sponsorInfo.text.isEmpty() && !data.additionalInfo.text.isEmpty()) ? InfoList{ data.sponsorInfo, data.additionalInfo } : !data.sponsorInfo.text.isEmpty() @@ -451,6 +433,12 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails( .msgId = data.msgId, .info = std::move(info), .externalLink = data.externalLink, + .isForceUserpicDisplay = data.from.isForceUserpicDisplay, + .buttonText = !data.externalLink.isEmpty() + ? tr::lng_view_button_external_link(tr::now) + : data.from.isBot + ? tr::lng_view_button_bot(tr::now) + : QString(), }; } diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.h b/Telegram/SourceFiles/data/data_sponsored_messages.h index a01126a12..b67c46d7f 100644 --- a/Telegram/SourceFiles/data/data_sponsored_messages.h +++ b/Telegram/SourceFiles/data/data_sponsored_messages.h @@ -32,7 +32,7 @@ struct SponsoredFrom { bool isExactPost = false; bool isRecommended = false; QString externalLink; - ImageWithLocation userpic; + PhotoId externalLinkPhotoId; bool isForceUserpicDisplay = false; }; @@ -61,6 +61,8 @@ public: MsgId msgId; std::vector info; QString externalLink; + bool isForceUserpicDisplay = false; + QString buttonText; }; using RandomId = QByteArray; explicit SponsoredMessages(not_null owner); diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index a0e95eea5..ab72828a3 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -320,6 +320,7 @@ enum class MediaWebPageFlag : uint8 { ForceSmallMedia = (1 << 1), Manual = (1 << 2), Safe = (1 << 3), + Sponsored = (1 << 4), }; inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; } using MediaWebPageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 6b6705c8f..32cae6df1 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -695,13 +695,42 @@ HistoryItem::HistoryItem( ? injectedAfter->date() : 0), /*from.peer ? from.peer->id : */PeerId(0)) { - createComponentsHelper( - _flags, - FullReplyTo(), - UserId(0), // viaBotId - QString(), // postAuthor - HistoryMessageMarkupData()); - setText(textWithEntities); + _flags |= MessageFlag::Sponsored; + + const auto webPageType = from.isExactPost + ? WebPageType::Message + : from.isBot + ? WebPageType::Bot + : from.isBroadcast + ? WebPageType::Channel + : (from.peer && from.peer->isUser()) + ? WebPageType::User + : WebPageType::Group; + + const auto webpage = history->peer->owner().webpage( + history->peer->owner().nextLocalMessageId().bare, + webPageType, + from.externalLink, + from.externalLink, + from.isRecommended + ? tr::lng_recommended_message_title(tr::now) + : tr::lng_sponsored_message_title(tr::now), + from.title, + textWithEntities, + from.externalLinkPhotoId + ? history->owner().photo(from.externalLinkPhotoId) + : nullptr, + nullptr, + WebPageCollage(), + 0, + QString(), + false, + 0); + auto webpageMedia = std::make_unique( + this, + webpage, + MediaWebPageFlag::Sponsored); + _media = std::move(webpageMedia); setSponsoredFrom(from); } diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 57862872a..c008fac99 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -445,11 +445,14 @@ QSize BottomInfo::countCurrentSize(int newWidth) { if (newWidth >= maxWidth()) { return optimalSize(); } + const auto dateHeight = (_data.flags & Data::Flag::Sponsored) + ? 0 + : st::msgDateFont->height; const auto noReactionsWidth = maxWidth() - _reactionsMaxWidth; accumulate_min(newWidth, std::max(noReactionsWidth, _reactionsMaxWidth)); return QSize( newWidth, - st::msgDateFont->height + countReactionsHeight(newWidth)); + dateHeight + countReactionsHeight(newWidth)); } void BottomInfo::layout() { @@ -478,10 +481,8 @@ void BottomInfo::layoutDateText() { const auto name = _authorElided ? st::msgDateFont->elided(author, maxWidth - afterAuthorWidth) : author; - const auto full = (_data.flags & Data::Flag::Recommended) - ? tr::lng_recommended(tr::now) - : (_data.flags & Data::Flag::Sponsored) - ? tr::lng_sponsored(tr::now) + const auto full = (_data.flags & Data::Flag::Sponsored) + ? QString() : (_data.flags & Data::Flag::Imported) ? (date + ' ' + tr::lng_imported(tr::now)) : name.isEmpty() @@ -568,7 +569,10 @@ QSize BottomInfo::countOptimalSize() { } _reactionsMaxWidth = countReactionsMaxWidth(); width += _reactionsMaxWidth; - return QSize(width, st::msgDateFont->height); + const auto dateHeight = (_data.flags & Data::Flag::Sponsored) + ? 0 + : st::msgDateFont->height; + return QSize(width, dateHeight); } BottomInfo::Reaction BottomInfo::prepareReactionWithId( @@ -644,10 +648,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null message) { if (message->context() == Context::Replies) { result.flags |= Flag::RepliesContext; } - if (const auto sponsored = item->Get()) { - if (sponsored->recommended) { - result.flags |= Flag::Recommended; - } + if (item->isSponsored()) { result.flags |= Flag::Sponsored; } if (item->isPinned() && message->context() != Context::Pinned) { diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index b0d28c2a1..d593063ef 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -44,7 +44,6 @@ public: Sponsored = 0x10, Pinned = 0x20, Imported = 0x40, - Recommended = 0x80, //Unread, // We don't want to pass and update it in Date for now. }; friend inline constexpr bool is_flag_type(Flag) { return true; }; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index c8f00f853..c7b0dc7c4 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1996,14 +1996,6 @@ bool Message::hasFromPhoto() const { case Context::Replies: { const auto item = data(); if (item->isPost()) { - if (item->isSponsored()) { - if (item->history()->peer->isMegagroup()) { - return true; - } - if (const auto info = item->Get()) { - return info->isForceUserpicDisplay; - } - } return false; } if (item->isEmpty() @@ -3098,10 +3090,8 @@ int Message::viewButtonHeight() const { void Message::updateViewButtonExistence() { const auto item = data(); - const auto sponsored = item->Get(); - const auto media = sponsored ? nullptr : item->media(); - const auto has = sponsored - || (media && ViewButton::MediaHasViewButton(media)); + const auto media = item->media(); + const auto has = (media && ViewButton::MediaHasViewButton(media)); if (!has) { _viewButton = nullptr; return; @@ -3114,7 +3104,7 @@ void Message::updateViewButtonExistence() { colorIndex(), [=] { repaint(); }); }; - _viewButton = sponsored ? make(sponsored) : make(media); + _viewButton = make(media); } void Message::initLogEntryOriginal() { @@ -3200,7 +3190,7 @@ bool Message::hasFromName() const { } bool Message::displayFromName() const { - if (!hasFromName() || isAttachedToPrevious()) { + if (!hasFromName() || isAttachedToPrevious() || data()->isSponsored()) { return false; } return !Has(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index c622585b9..6b9e291d3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -13,9 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/history_item.h" #include "history/history.h" -#include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" -#include "history/view/history_view_view_button.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_sponsored_click_handler.h" #include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_theme_document.h" #include "ui/image/image.h" @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/painter.h" #include "ui/power_saving.h" +#include "main/main_session.h" #include "data/data_session.h" #include "data/data_wall_paper.h" #include "data/data_media_types.h" @@ -35,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo_media.h" #include "data/data_file_click_handler.h" #include "data/data_file_origin.h" +#include "data/data_sponsored_messages.h" #include "styles/style_chat.h" namespace HistoryView { @@ -117,23 +119,7 @@ std::vector> PrepareCollageMedia( : QString()); } -} // namespace - -WebPage::WebPage( - not_null parent, - not_null data, - MediaWebPageFlags flags) -: Media(parent) -, _st(st::historyPagePreview) -, _data(data) -, _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right()) -, _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) -, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) -, _flags(flags) { - history()->owner().registerWebPageView(_data, _parent); -} - -bool WebPage::HasButton(not_null webpage) { +[[nodiscard]] bool HasButton(not_null webpage) { const auto type = webpage->type; return (type == WebPageType::Message) || (type == WebPageType::Group) @@ -154,6 +140,43 @@ bool WebPage::HasButton(not_null webpage) { && webpage->document->isWallPaper()); } +} // namespace + +WebPage::WebPage( + not_null parent, + not_null data, + MediaWebPageFlags flags) +: Media(parent) +, _st(st::historyPagePreview) +, _data(data) +, _sponsoredData([&]() -> std::optional { + if (!(flags & MediaWebPageFlag::Sponsored)) { + return std::nullopt; + } + const auto &data = _parent->data()->history()->owner(); + const auto details = data.sponsoredMessages().lookupDetails( + _parent->data()->fullId()); + auto result = std::make_optional(); + result->buttonText = details.buttonText; + result->hasExternalLink = (details.externalLink == _data->url); +#ifdef _DEBUG + if (details.peer) { +#else + if (details.isForceUserpicDisplay && details.peer) { +#endif + result->peer = details.peer; + result->userpicView = details.peer->createUserpicView(); + details.peer->loadUserpic(); + } + return result; +}()) +, _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right()) +, _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) +, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) +, _flags(flags) { + history()->owner().registerWebPageView(_data, _parent); +} + QSize WebPage::countOptimalSize() { if (_data->pendingTill || _data->failed) { return { 0, 0 }; @@ -165,6 +188,11 @@ QSize WebPage::countOptimalSize() { if (HasButton(_data)) { _openButton = PageToPhrase(_data); _openButtonWidth = st::semiboldFont->width(_openButton); + } else if (_sponsoredData) { + if (!_sponsoredData->buttonText.isEmpty()) { + _openButton = Ui::Text::Upper(_sponsoredData->buttonText); + _openButtonWidth = st::semiboldFont->width(_openButton); + } } const auto padding = inBubblePadding() + innerMargin(); @@ -183,7 +211,7 @@ QSize WebPage::countOptimalSize() { } auto lineHeight = UnitedLineHeight(); - if (!_openl && !_data->url.isEmpty()) { + if (!_openl && (!_data->url.isEmpty() || _sponsoredData)) { const auto previewOfHiddenUrl = [&] { if (_data->type == WebPageType::BotApp) { // Bot Web Apps always show confirmation on hidden urls. @@ -233,6 +261,11 @@ QSize WebPage::countOptimalSize() { _data->document, _parent->data()->fullId()); } + if (_sponsoredData) { + _openl = SponsoredLink(_sponsoredData->hasExternalLink + ? _data->url + : QString()); + } } // init layout @@ -375,13 +408,20 @@ QSize WebPage::countCurrentSize(int newWidth) { auto newHeight = 0; auto lineHeight = UnitedLineHeight(); - auto linesMax = isLogEntryOriginal() ? kMaxOriginalEntryLines : 5; + auto linesMax = (_sponsoredData || isLogEntryOriginal()) + ? kMaxOriginalEntryLines + : 5; auto siteNameHeight = _siteNameLines ? lineHeight : 0; - if (asArticle()) { - _pixh = linesMax * lineHeight; + const auto asSponsored = (!!_sponsoredData); + if (asArticle() || asSponsored) { + const auto article = asArticle(); + constexpr auto kSponsoredUserpicLines = 2; + _pixh = (asSponsored ? kSponsoredUserpicLines : linesMax) * lineHeight; do { - _pixw = articleThumbWidth(_data->photo, _pixh); - auto wleft = innerWidth - st::webPagePhotoDelta - qMax(_pixw, lineHeight); + _pixw = asSponsored ? _pixh : articleThumbWidth(_data->photo, _pixh); + auto wleft = asSponsored + ? innerWidth - st::webPagePhotoDelta - qMax(_pixw, lineHeight) + : innerWidth; newHeight = siteNameHeight; @@ -494,7 +534,9 @@ void WebPage::ensurePhotoMediaCreated() const { } bool WebPage::hasHeavyPart() const { - return _photoMedia || (_attach ? _attach->hasHeavyPart() : false); + return _photoMedia + || (_sponsoredData && !_sponsoredData->userpicView.null()) + || (_attach ? _attach->hasHeavyPart() : false); } void WebPage::unloadHeavyPart() { @@ -503,6 +545,9 @@ void WebPage::unloadHeavyPart() { } _description.unloadPersistentAnimation(); _photoMedia = nullptr; + if (_sponsoredData) { + _sponsoredData->userpicView = Ui::PeerUserpicView(); + } } void WebPage::draw(Painter &p, const PaintContext &context) const { @@ -576,6 +621,21 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { st->msgSelectOverlayCorners(Ui::CachedCornerRadius::Small)); } paintw -= pw + st::webPagePhotoDelta; + } else if (_sponsoredData && _sponsoredData->peer) { + const auto size = _pixh; + const auto sizeHq = size * style::DevicePixelRatio(); + const auto userpicPos = QPoint(inner.left() + paintw - size, tshift); + const auto &peer = _sponsoredData->peer; + auto &view = _sponsoredData->userpicView; + if (const auto cloud = peer->userpicCloudImage(view)) { + Ui::ValidateUserpicCache(view, cloud, nullptr, sizeHq, true); + p.drawImage(QRect(userpicPos, QSize(size, size)), view.cached); + } else { + const auto r = sizeHq * Ui::ForumUserpicRadiusMultiplier(); + const auto empty = peer->generateUserpicImage(view, sizeHq, r); + p.drawImage(QRect(userpicPos, QSize(size, size)), empty); + } + // paintw -= size + st::webPagePhotoDelta; } if (_siteNameLines) { p.setPen(cache->icon); @@ -707,6 +767,9 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { const auto bubble = _attach ? _attach->bubbleMargins() : QMargins(); const auto full = QRect(0, 0, width(), height()); auto outer = full.marginsRemoved(inBubblePadding()); + if (_sponsoredData) { + outer.translate(0, st::msgDateFont->height); + } auto inner = outer.marginsRemoved(innerMargin()); auto tshift = inner.top(); auto paintw = inner.width(); @@ -790,7 +853,7 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { } } } - if (!result.link && outer.contains(point)) { + if ((!result.link || _sponsoredData) && outer.contains(point)) { result.link = _openl; } _lastPoint = point - outer.topLeft(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.h b/Telegram/SourceFiles/history/view/media/history_view_web_page.h index bf9254e65..6f6003124 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "history/view/media/history_view_media.h" +#include "ui/userpic_view.h" namespace Data { class Media; @@ -27,8 +28,6 @@ public: not_null data, MediaWebPageFlags flags); - [[nodiscard]] static bool HasButton(not_null data); - void refreshParentId(not_null realParent) override; void draw(Painter &p, const PaintContext &context) const override; @@ -131,6 +130,14 @@ private: mutable std::shared_ptr _photoMedia; mutable std::unique_ptr _ripple; + struct SponsoredData final { + PeerData *peer = nullptr; + Ui::PeerUserpicView userpicView; + QString buttonText; + bool hasExternalLink = false; + }; + mutable std::optional _sponsoredData; + int _dataVersion = -1; int _siteNameLines = 0; int _descriptionLines = 0;