diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 93b5217bb..9b9b567ee 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -631,6 +631,8 @@ PRIVATE history/view/history_view_service_message.h history/view/history_view_top_bar_widget.cpp history/view/history_view_top_bar_widget.h + history/view/history_view_view_button.cpp + history/view/history_view_view_button.h history/view/history_view_webpage_preview.cpp history/view/history_view_webpage_preview.h history/history.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3d77a1a07..01003683b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2889,6 +2889,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_voice_speed_fast" = "Fast"; "lng_voice_speed_very_fast" = "Very fast"; +"lng_view_button_user" = "View user"; +"lng_view_button_bot" = "View bot"; +"lng_view_button_group" = "View group"; +"lng_view_button_channel" = "View channel"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 05e29e092..b182b4f63 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_web_page.h" #include "history/view/history_view_group_call_tracker.h" // UserpicInRow. +#include "history/view/history_view_view_button.h" // ViewButton. #include "history/history.h" #include "ui/effects/ripple_animation.h" #include "base/unixtime.h" @@ -242,6 +243,12 @@ Message::Message( : Element(delegate, data, replacing) { initLogEntryOriginal(); initPsa(); + + if (data->isSponsored()) { + _viewButton = std::make_unique( + data->displayFrom(), + [=] { history()->owner().requestViewRepaint(this); }); + } } Message::~Message() { @@ -604,6 +611,12 @@ void Message::draw(Painter &p, const PaintContext &context) const { auto inner = g; paintCommentsButton(p, inner, context); + if (_viewButton) { + _viewButton->draw( + p, + _viewButton->countSponsoredRect(inner), + context); + } auto trect = inner.marginsRemoved(st::msgPadding); if (mediaOnBottom) { @@ -1066,10 +1079,12 @@ void Message::clickHandlerPressedChanged( bool pressed) { Element::clickHandlerPressedChanged(handler, pressed); - if (!handler || !_comments) { + if (!handler) { return; - } else if (handler == _comments->link) { + } else if (_comments && (handler == _comments->link)) { toggleCommentsButtonRipple(pressed); + } else if (_viewButton) { + _viewButton->checkLink(handler, pressed); } } @@ -1190,6 +1205,13 @@ TextState Message::textState( if (getStateCommentsButton(point, bubble, &result)) { return result; } + if (_viewButton + && _viewButton->getState( + point, + _viewButton->countSponsoredRect(bubble), + &result)) { + return result; + } auto trect = bubble.marginsRemoved(st::msgPadding); if (mediaOnBottom) { @@ -1732,7 +1754,8 @@ void Message::drawInfo( ; msgsigned && !msgsigned->isAnonymousRank) { msgsigned->signature.drawElided(p, dateX, dateY, item->_timeWidth); } else if (const auto sponsored = displayedSponsorBadge()) { - sponsored->text.drawElided(p, dateX, dateY, item->_timeWidth); + const auto skipY = _viewButton ? _viewButton->height() : 0; + sponsored->text.drawElided(p, dateX, dateY - skipY, item->_timeWidth); } else if (const auto edited = displayedEditBadge()) { edited->text.drawElided(p, dateX, dateY, item->_timeWidth); } else { @@ -1970,6 +1993,8 @@ bool Message::toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const { if (_comments && _comments->link == handler) { return true; + } else if (_viewButton && _viewButton->link() == handler) { + return true; } else if (const auto media = this->media()) { if (media->toggleSelectionByHandlerClick(handler)) { return true; @@ -2593,6 +2618,9 @@ int Message::resizeContentGetHeight(int newWidth) { if (item->repliesAreComments() || item->externalReply()) { newHeight += st::historyCommentsButtonHeight; } + if (_viewButton) { + newHeight += _viewButton->height(); + } } else if (mediaDisplayed) { newHeight = media->height(); } else { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index e0914fc41..5e5ebcd03 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -18,6 +18,7 @@ struct HistoryMessageForwarded; namespace HistoryView { +class ViewButton; class WebPage; // Special type of Component for the channel actions log. @@ -223,6 +224,7 @@ private: mutable ClickHandlerPtr _rightActionLink; mutable ClickHandlerPtr _fastReplyLink; mutable std::unique_ptr _comments; + std::unique_ptr _viewButton; Ui::Text::String _rightBadge; int _bubbleWidthLimit = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp new file mode 100644 index 000000000..8c4ecdc7b --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -0,0 +1,167 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/history_view_view_button.h" + +#include "core/click_handler_types.h" +#include "data/data_user.h" +#include "history/view/history_view_cursor_state.h" +#include "lang/lang_keys.h" +#include "ui/click_handler.h" +#include "ui/effects/ripple_animation.h" +#include "ui/round_rect.h" +#include "ui/text/text_utilities.h" // Ui::Text::ToUpper +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_widgets.h" +#include "styles/style_window.h" + +namespace HistoryView { +namespace { + +inline auto PeerToPhrase(not_null peer) { + const auto phrase = [&] { + if (const auto user = peer->asUser()) { + return user->isBot() + ? tr::lng_view_button_bot + : tr::lng_view_button_user; + } else if (peer->isChat()) { + return tr::lng_view_button_group; + } else if (peer->isChannel()) { + return tr::lng_view_button_channel; + } + Unexpected("Invalid peer in ViewButton."); + }()(tr::now); + return Ui::Text::Upper(phrase); +} + +} // namespace + +struct ViewButton::Inner { + Inner(not_null peer, Fn updateCallback); + void updateMask(int height); + void toggleRipple(bool pressed); + + const style::margins &margins; + const ClickHandlerPtr link; + const Fn updateCallback; + int lastWidth = 0; + QPoint lastPoint; + std::unique_ptr ripple; + Ui::Text::String text; +}; + +ViewButton::Inner::Inner(not_null peer, Fn updateCallback) +: margins(st::historyViewButtonMargins) +, link(peer->openLink()) +, updateCallback(std::move(updateCallback)) +, text(st::historyViewButtonTextStyle, PeerToPhrase(peer)) { +} + +void ViewButton::Inner::updateMask(int height) { + ripple = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::roundRectMask( + QSize(lastWidth, height - margins.top() - margins.bottom()), + st::roundRadiusLarge), + updateCallback); +} + +void ViewButton::Inner::toggleRipple(bool pressed) { + if (ripple) { + if (pressed) { + ripple->add(lastPoint); + } else { + ripple->lastStop(); + } + } +} + +ViewButton::ViewButton(not_null peer, Fn updateCallback) +: _inner(std::make_unique(peer, std::move(updateCallback))) { +} + +ViewButton::~ViewButton() { +} + +void ViewButton::resized() const { + _inner->updateMask(height()); +} + +int ViewButton::height() const { + return st::historyViewButtonHeight; +} + +void ViewButton::draw( + Painter &p, + const QRect &r, + const Ui::ChatPaintContext &context) { + const auto stm = context.messageStyle(); + + if (_inner->ripple && !_inner->ripple->empty()) { + const auto opacity = p.opacity(); + p.setOpacity(st::historyPollRippleOpacity); + const auto colorOverride = &stm->msgWaveformInactive->c; + _inner->ripple->paint(p, r.left(), r.top(), r.width(), colorOverride); + p.setOpacity(opacity); + } + + p.save(); + { + PainterHighQualityEnabler hq(p); + p.setPen(stm->fwdTextPalette.linkFg); + p.setBrush(Qt::NoBrush); + p.drawRoundedRect(r, st::roundRadiusLarge, st::roundRadiusLarge); + + _inner->text.drawElided( + p, + r.left(), + r.top() + (r.height() - _inner->text.minHeight()) / 2, + r.width(), + 1, + style::al_center); + } + p.restore(); + if (_inner->lastWidth != r.width()) { + _inner->lastWidth = r.width(); + resized(); + } +} + +const ClickHandlerPtr &ViewButton::link() const { + return _inner->link; +} + +bool ViewButton::checkLink(const ClickHandlerPtr &other, bool pressed) { + if (_inner->link != other) { + return false; + } + _inner->toggleRipple(pressed); + return true; +} + +bool ViewButton::getState( + QPoint point, + const QRect &g, + not_null outResult) const { + if (!g.contains(point)) { + return false; + } + outResult->link = _inner->link; + _inner->lastPoint = point - g.topLeft(); + return true; +} + +QRect ViewButton::countSponsoredRect(const QRect &r) const { + return QRect( + r.left(), + r.top() + r.height() - height(), + r.width(), + height()) - _inner->margins; +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.h b/Telegram/SourceFiles/history/view/history_view_view_button.h new file mode 100644 index 000000000..8a63b6b2f --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_view_button.h @@ -0,0 +1,45 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/chat/chat_style.h" + +namespace HistoryView { + +struct TextState; + +class ViewButton { +public: + ViewButton(not_null peer, Fn updateCallback); + ~ViewButton(); + + [[nodiscard]] int height() const; + + void draw( + Painter &p, + const QRect &r, + const Ui::ChatPaintContext &context); + + [[nodiscard]] const ClickHandlerPtr &link() const; + bool checkLink(const ClickHandlerPtr &other, bool pressed); + + [[nodiscard]] QRect countSponsoredRect(const QRect &r) const; + + [[nodiscard]] bool getState( + QPoint point, + const QRect &g, + not_null outResult) const; + +private: + void resized() const; + + struct Inner; + const std::unique_ptr _inner; +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 8e6b6f0b6..99132df93 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -750,6 +750,11 @@ historyPollOutChosenSelected: icon {{ "poll_select_check", historyFileOutIconFgS historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }}; historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }}; +historyViewButtonHeight: 42px; +historyViewButtonMargins: margins(5px, 5px, 5px, 5px); +historyViewButtonOutline: margins(2px, 2px, 2px, 2px); +historyViewButtonTextStyle: defaultTextStyle; + historyCommentsButtonHeight: 40px; historyCommentsSkipLeft: 9px; historyCommentsSkipText: 10px;