Added view button to webpages.

This commit is contained in:
23rd 2021-10-10 19:11:08 +03:00
parent 1613495425
commit 6163e922b3
10 changed files with 183 additions and 21 deletions

View File

@ -2893,6 +2893,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_view_button_bot" = "View bot"; "lng_view_button_bot" = "View bot";
"lng_view_button_group" = "View group"; "lng_view_button_group" = "View group";
"lng_view_button_channel" = "View channel"; "lng_view_button_channel" = "View channel";
"lng_view_button_background" = "View background";
"lng_view_button_theme" = "View theme";
"lng_view_button_message" = "View message";
"lng_view_button_voice_chat" = "Voice chat";
"lng_view_button_voice_chat_channel" = "Live stream";
"lng_sponsored_title" = "What are sponsored messages?"; "lng_sponsored_title" = "What are sponsored messages?";
"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties cant spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:"; "lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties cant spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:";

View File

@ -146,6 +146,20 @@ WebPageType ParseWebPageType(const MTPDwebPage &page) {
return WebPageType::WallPaper; return WebPageType::WallPaper;
} else if (type == qstr("telegram_theme")) { } else if (type == qstr("telegram_theme")) {
return WebPageType::Theme; return WebPageType::Theme;
} else if (type == qstr("telegram_channel")) {
return WebPageType::Channel;
} else if (type == qstr("telegram_message")) {
return WebPageType::Message;
} else if (type == qstr("telegram_bot")) {
return WebPageType::Bot;
} else if (type == qstr("telegram_megagroup")) {
return WebPageType::Group;
} else if (type == qstr("telegram_voicechat")) {
return WebPageType::VoiceChat;
} else if (type == qstr("telegram_livestream")) {
return WebPageType::Livestream;
} else if (type == qstr("telegram_user")) {
return WebPageType::User;
} else if (page.vcached_page()) { } else if (page.vcached_page()) {
return WebPageType::ArticleWithIV; return WebPageType::ArticleWithIV;
} else { } else {
@ -302,4 +316,4 @@ void WebPageData::ApplyChanges(
}); });
} }
session->data().sendWebPageGamePollNotifications(); session->data().sendWebPageGamePollNotifications();
} }

View File

@ -17,13 +17,26 @@ class Session;
} // namespace Data } // namespace Data
enum class WebPageType { enum class WebPageType {
Message,
Group,
Channel,
Photo, Photo,
Video, Video,
User,
Bot,
Profile, Profile,
WallPaper, WallPaper,
Theme, Theme,
Article, Article,
ArticleWithIV, ArticleWithIV,
VoiceChat,
Livestream,
}; };
WebPageType ParseWebPageType(const MTPDwebPage &type); WebPageType ParseWebPageType(const MTPDwebPage &type);

View File

@ -243,12 +243,6 @@ Message::Message(
: Element(delegate, data, replacing) { : Element(delegate, data, replacing) {
initLogEntryOriginal(); initLogEntryOriginal();
initPsa(); initPsa();
if (data->isSponsored()) {
_viewButton = std::make_unique<ViewButton>(
data->displayFrom(),
[=] { history()->owner().requestViewRepaint(this); });
}
} }
Message::~Message() { Message::~Message() {
@ -611,16 +605,18 @@ void Message::draw(Painter &p, const PaintContext &context) const {
auto inner = g; auto inner = g;
paintCommentsButton(p, inner, context); paintCommentsButton(p, inner, context);
if (_viewButton) { if (ensureViewButton()) {
_viewButton->draw( _viewButton->draw(
p, p,
_viewButton->countSponsoredRect(inner), _viewButton->countRect(inner),
context); context);
} }
auto trect = inner.marginsRemoved(st::msgPadding); auto trect = inner.marginsRemoved(st::msgPadding);
if (mediaOnBottom) { if (mediaOnBottom) {
trect.setHeight(trect.height() + st::msgPadding.bottom()); trect.setHeight(trect.height()
+ st::msgPadding.bottom()
- viewButtonHeight());
} }
if (mediaOnTop) { if (mediaOnTop) {
trect.setY(trect.y() - st::msgPadding.top()); trect.setY(trect.y() - st::msgPadding.top());
@ -1208,14 +1204,16 @@ TextState Message::textState(
if (_viewButton if (_viewButton
&& _viewButton->getState( && _viewButton->getState(
point, point,
_viewButton->countSponsoredRect(bubble), _viewButton->countRect(bubble),
&result)) { &result)) {
return result; return result;
} }
auto trect = bubble.marginsRemoved(st::msgPadding); auto trect = bubble.marginsRemoved(st::msgPadding);
if (mediaOnBottom) { if (mediaOnBottom) {
trect.setHeight(trect.height() + st::msgPadding.bottom()); trect.setHeight(trect.height()
+ st::msgPadding.bottom()
- viewButtonHeight());
} }
if (mediaOnTop) { if (mediaOnTop) {
trect.setY(trect.y() - st::msgPadding.top()); trect.setY(trect.y() - st::msgPadding.top());
@ -1754,7 +1752,7 @@ void Message::drawInfo(
; msgsigned && !msgsigned->isAnonymousRank) { ; msgsigned && !msgsigned->isAnonymousRank) {
msgsigned->signature.drawElided(p, dateX, dateY, item->_timeWidth); msgsigned->signature.drawElided(p, dateX, dateY, item->_timeWidth);
} else if (const auto sponsored = displayedSponsorBadge()) { } else if (const auto sponsored = displayedSponsorBadge()) {
const auto skipY = _viewButton ? _viewButton->height() : 0; const auto skipY = viewButtonHeight();
sponsored->text.drawElided(p, dateX, dateY - skipY, item->_timeWidth); sponsored->text.drawElided(p, dateX, dateY - skipY, item->_timeWidth);
} else if (const auto edited = displayedEditBadge()) { } else if (const auto edited = displayedEditBadge()) {
edited->text.drawElided(p, dateX, dateY, item->_timeWidth); edited->text.drawElided(p, dateX, dateY, item->_timeWidth);
@ -1958,6 +1956,33 @@ int Message::monospaceMaxWidth() const {
+ st::msgPadding.right(); + st::msgPadding.right();
} }
int Message::viewButtonHeight() const {
return _viewButton ? _viewButton->height() : 0;
}
bool Message::ensureViewButton() const {
if (data()->isSponsored()
|| (data()->media()
&& ViewButton::MediaHasViewButton(data()->media()))) {
if (_viewButton) {
return true;
}
auto callback = [=] { history()->owner().requestViewRepaint(this); };
_viewButton = data()->isSponsored()
? std::make_unique<ViewButton>(
data()->displayFrom(),
std::move(callback))
: std::make_unique<ViewButton>(
data()->media(),
std::move(callback));
return true;
}
if (_viewButton) {
_viewButton.reset(nullptr);
}
return false;
}
void Message::initLogEntryOriginal() { void Message::initLogEntryOriginal() {
if (const auto log = message()->Get<HistoryMessageLogEntryOriginal>()) { if (const auto log = message()->Get<HistoryMessageLogEntryOriginal>()) {
AddComponents(LogEntryOriginal::Bit()); AddComponents(LogEntryOriginal::Bit());
@ -2618,8 +2643,8 @@ int Message::resizeContentGetHeight(int newWidth) {
if (item->repliesAreComments() || item->externalReply()) { if (item->repliesAreComments() || item->externalReply()) {
newHeight += st::historyCommentsButtonHeight; newHeight += st::historyCommentsButtonHeight;
} }
if (_viewButton) { if (ensureViewButton()) {
newHeight += _viewButton->height(); newHeight += viewButtonHeight();
} }
} else if (mediaDisplayed) { } else if (mediaDisplayed) {
newHeight = media->height(); newHeight = media->height();

View File

@ -213,6 +213,9 @@ private:
[[nodiscard]] int plainMaxWidth() const; [[nodiscard]] int plainMaxWidth() const;
[[nodiscard]] int monospaceMaxWidth() const; [[nodiscard]] int monospaceMaxWidth() const;
[[nodiscard]] bool ensureViewButton() const;
[[nodiscard]] int viewButtonHeight() const;
WebPage *logEntryOriginal() const; WebPage *logEntryOriginal() const;
[[nodiscard]] ClickHandlerPtr createGoToCommentsLink() const; [[nodiscard]] ClickHandlerPtr createGoToCommentsLink() const;
@ -224,7 +227,7 @@ private:
mutable ClickHandlerPtr _rightActionLink; mutable ClickHandlerPtr _rightActionLink;
mutable ClickHandlerPtr _fastReplyLink; mutable ClickHandlerPtr _fastReplyLink;
mutable std::unique_ptr<CommentsButton> _comments; mutable std::unique_ptr<CommentsButton> _comments;
std::unique_ptr<ViewButton> _viewButton; mutable std::unique_ptr<ViewButton> _viewButton;
Ui::Text::String _rightBadge; Ui::Text::String _rightBadge;
int _bubbleWidthLimit = 0; int _bubbleWidthLimit = 0;

View File

@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "history/view/history_view_view_button.h" #include "history/view/history_view_view_button.h"
#include "core/application.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "data/data_cloud_themes.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_sponsored_messages.h" #include "data/data_sponsored_messages.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_web_page.h"
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -42,22 +45,71 @@ inline auto PeerToPhrase(not_null<PeerData*> peer) {
return Ui::Text::Upper(phrase); return Ui::Text::Upper(phrase);
} }
inline auto WebPageToPhrase(not_null<WebPageData*> webpage) {
const auto type = webpage->type;
return Ui::Text::Upper((type == WebPageType::Theme)
? tr::lng_view_button_theme(tr::now)
: (type == WebPageType::Message)
? tr::lng_view_button_message(tr::now)
: (type == WebPageType::Group)
? tr::lng_view_button_group(tr::now)
: (type == WebPageType::WallPaper)
? tr::lng_view_button_background(tr::now)
: (type == WebPageType::Channel)
? tr::lng_view_button_channel(tr::now)
: (type == WebPageType::VoiceChat)
? tr::lng_view_button_voice_chat(tr::now)
: (type == WebPageType::Livestream)
? tr::lng_view_button_voice_chat_channel(tr::now)
: (type == WebPageType::Bot)
? tr::lng_view_button_bot(tr::now)
: (type == WebPageType::User)
? tr::lng_view_button_user(tr::now)
: QString());
}
} // namespace } // namespace
struct ViewButton::Inner { struct ViewButton::Inner {
Inner(not_null<PeerData*> peer, Fn<void()> updateCallback); Inner(not_null<PeerData*> peer, Fn<void()> updateCallback);
Inner(not_null<Data::Media*> media, Fn<void()> updateCallback);
void updateMask(int height); void updateMask(int height);
void toggleRipple(bool pressed); void toggleRipple(bool pressed);
const style::margins &margins; const style::margins &margins;
const ClickHandlerPtr link; const ClickHandlerPtr link;
const Fn<void()> updateCallback; const Fn<void()> updateCallback;
bool underDate = true;
int lastWidth = 0; int lastWidth = 0;
QPoint lastPoint; QPoint lastPoint;
std::unique_ptr<Ui::RippleAnimation> ripple; std::unique_ptr<Ui::RippleAnimation> ripple;
Ui::Text::String text; Ui::Text::String text;
}; };
bool ViewButton::MediaHasViewButton(not_null<Data::Media*> media) {
return media->webpage()
? MediaHasViewButton(media->webpage())
: false;
}
bool ViewButton::MediaHasViewButton(
not_null<WebPageData*> webpage) {
const auto type = webpage->type;
return (type == WebPageType::Message)
|| (type == WebPageType::Group)
|| (type == WebPageType::Channel)
// || (type == WebPageType::Bot)
// || (type == WebPageType::User)
|| (type == WebPageType::VoiceChat)
|| (type == WebPageType::Livestream)
|| ((type == WebPageType::Theme)
&& webpage->document
&& webpage->document->isTheme())
|| ((type == WebPageType::WallPaper)
&& webpage->document
&& webpage->document->isWallPaper());
}
ViewButton::Inner::Inner(not_null<PeerData*> peer, Fn<void()> updateCallback) ViewButton::Inner::Inner(not_null<PeerData*> peer, Fn<void()> updateCallback)
: margins(st::historyViewButtonMargins) : margins(st::historyViewButtonMargins)
, link(std::make_shared<LambdaClickHandler>([=](ClickContext context) { , link(std::make_shared<LambdaClickHandler>([=](ClickContext context) {
@ -73,6 +125,26 @@ ViewButton::Inner::Inner(not_null<PeerData*> peer, Fn<void()> updateCallback)
, text(st::historyViewButtonTextStyle, PeerToPhrase(peer)) { , text(st::historyViewButtonTextStyle, PeerToPhrase(peer)) {
} }
ViewButton::Inner::Inner(
not_null<Data::Media*> media,
Fn<void()> updateCallback)
: margins(st::historyViewButtonMargins)
, link(std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
const auto &data = controller->session().data();
const auto webpage = media->webpage();
if (!webpage) {
return;
}
HiddenUrlClickHandler::Open(webpage->url, context.other);
}
}))
, updateCallback(std::move(updateCallback))
, underDate(false)
, text(st::historyViewButtonTextStyle, WebPageToPhrase(media->webpage())) {
}
void ViewButton::Inner::updateMask(int height) { void ViewButton::Inner::updateMask(int height) {
ripple = std::make_unique<Ui::RippleAnimation>( ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation, st::defaultRippleAnimation,
@ -96,6 +168,12 @@ ViewButton::ViewButton(not_null<PeerData*> peer, Fn<void()> updateCallback)
: _inner(std::make_unique<Inner>(peer, std::move(updateCallback))) { : _inner(std::make_unique<Inner>(peer, std::move(updateCallback))) {
} }
ViewButton::ViewButton(
not_null<Data::Media*> media,
Fn<void()> updateCallback)
: _inner(std::make_unique<Inner>(media, std::move(updateCallback))) {
}
ViewButton::~ViewButton() { ViewButton::~ViewButton() {
} }
@ -169,10 +247,11 @@ bool ViewButton::getState(
return true; return true;
} }
QRect ViewButton::countSponsoredRect(const QRect &r) const { QRect ViewButton::countRect(const QRect &r) const {
const auto dateHeight = (_inner->underDate ? 0 : st::msgDateFont->height);
return QRect( return QRect(
r.left(), r.left(),
r.top() + r.height() - height(), r.top() + r.height() - height() - dateHeight,
r.width(), r.width(),
height()) - _inner->margins; height()) - _inner->margins;
} }

View File

@ -9,6 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
namespace Data {
class Media;
} // namespace Data
struct WebPageData;
namespace HistoryView { namespace HistoryView {
struct TextState; struct TextState;
@ -16,8 +22,14 @@ struct TextState;
class ViewButton { class ViewButton {
public: public:
ViewButton(not_null<PeerData*> peer, Fn<void()> updateCallback); ViewButton(not_null<PeerData*> peer, Fn<void()> updateCallback);
ViewButton(not_null<Data::Media*> media, Fn<void()> updateCallback);
~ViewButton(); ~ViewButton();
[[nodiscard]] static bool MediaHasViewButton(
not_null<Data::Media*> media);
[[nodiscard]] static bool MediaHasViewButton(
not_null<WebPageData*> webpage);
[[nodiscard]] int height() const; [[nodiscard]] int height() const;
void draw( void draw(
@ -28,7 +40,7 @@ public:
[[nodiscard]] const ClickHandlerPtr &link() const; [[nodiscard]] const ClickHandlerPtr &link() const;
bool checkLink(const ClickHandlerPtr &other, bool pressed); bool checkLink(const ClickHandlerPtr &other, bool pressed);
[[nodiscard]] QRect countSponsoredRect(const QRect &r) const; [[nodiscard]] QRect countRect(const QRect &r) const;
[[nodiscard]] bool getState( [[nodiscard]] bool getState(
QPoint point, QPoint point,

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" #include "history/history.h"
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_view_button.h"
#include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_media_common.h"
#include "history/view/media/history_view_theme_document.h" #include "history/view/media/history_view_theme_document.h"
#include "ui/image/image.h" #include "ui/image/image.h"
@ -188,7 +189,12 @@ QSize WebPage::countOptimalSize() {
_data->url); _data->url);
} }
auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom(); _hasViewButton = ViewButton::MediaHasViewButton(_data);
const auto textFloatsAroundInfo = !_asArticle
&& !_attach
&& isBubbleBottom()
&& !_hasViewButton;
// init strings // init strings
if (_description.isEmpty() && !_data->description.text.isEmpty()) { if (_description.isEmpty() && !_data->description.text.isEmpty()) {
@ -391,6 +397,8 @@ QSize WebPage::countCurrentSize(int newWidth) {
} else if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { } else if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) {
newHeight += bottomInfoPadding(); newHeight += bottomInfoPadding();
} }
} else if (_hasViewButton) {
newHeight += bottomInfoPadding();
} }
} }
auto padding = inBubblePadding(); auto padding = inBubblePadding();
@ -471,6 +479,8 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
bshift += bottomInfoPadding(); bshift += bottomInfoPadding();
} else if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { } else if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
bshift += bottomInfoPadding(); bshift += bottomInfoPadding();
} else if (_hasViewButton) {
bshift += bottomInfoPadding();
} }
QRect bar(style::rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width())); QRect bar(style::rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width()));

View File

@ -117,6 +117,7 @@ private:
mutable std::shared_ptr<Data::PhotoMedia> _photoMedia; mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
bool _asArticle = false; bool _asArticle = false;
bool _hasViewButton = false;
int _dataVersion = -1; int _dataVersion = -1;
int _siteNameLines = 0; int _siteNameLines = 0;
int _titleLines = 0; int _titleLines = 0;

View File

@ -751,7 +751,7 @@ historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }};
historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }}; historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }};
historyViewButtonHeight: 42px; historyViewButtonHeight: 42px;
historyViewButtonMargins: margins(5px, 5px, 5px, 5px); historyViewButtonMargins: margins(13px, 5px, 13px, 5px);
historyViewButtonOutline: margins(2px, 2px, 2px, 2px); historyViewButtonOutline: margins(2px, 2px, 2px, 2px);
historyViewButtonTextStyle: semiboldTextStyle; historyViewButtonTextStyle: semiboldTextStyle;