diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 55ff76fa2..2f020b2e0 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1126,6 +1126,15 @@ PeerData *HistoryItem::displayFrom() const { return author().get(); } +uint8 HistoryItem::computeColorIndex() const { + if (const auto from = displayFrom()) { + return from->colorIndex(); + } else if (const auto info = hiddenSenderInfo()) { + return info->colorIndex; + } + Unexpected("No displayFrom and no hiddenSenderInfo."); +} + std::unique_ptr HistoryItem::createView( not_null delegate, HistoryView::Element *replacing) { @@ -3253,7 +3262,8 @@ void HistoryItem::setSponsoredFrom(const Data::SponsoredFrom &from) { const auto sponsored = Get(); sponsored->sender = std::make_unique( from.title, - false); + false, + from.peer ? from.peer->colorIndex() : std::optional()); sponsored->recommended = from.isRecommended; sponsored->isForceUserpicDisplay = from.isForceUserpicDisplay; if (from.userpic.location.valid()) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index c04e6f9de..24f6bc510 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -498,6 +498,7 @@ public: [[nodiscard]] bool isDiscussionPost() const; [[nodiscard]] HistoryItem *lookupDiscussionPostOriginal() const; [[nodiscard]] PeerData *displayFrom() const; + [[nodiscard]] uint8 computeColorIndex() const; [[nodiscard]] std::unique_ptr createView( not_null delegate, diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 71163b4e9..9968347b4 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -108,11 +108,15 @@ void HistoryMessageVia::resize(int32 availw) const { } } -HiddenSenderInfo::HiddenSenderInfo(const QString &name, bool external) +HiddenSenderInfo::HiddenSenderInfo( + const QString &name, + bool external, + std::optional colorIndex) : name(name) -, colorIndex(Data::DecideColorIndex(Data::FakePeerIdForJustName(name))) +, colorIndex(colorIndex.value_or( + Data::DecideColorIndex(Data::FakePeerIdForJustName(name)))) , emptyUserpic( - Ui::EmptyUserpic::UserpicColor(colorIndex), + Ui::EmptyUserpic::UserpicColor(this->colorIndex), (external ? Ui::EmptyUserpic::ExternalName() : name)) { @@ -399,13 +403,10 @@ bool HistoryMessageReply::updateData( } if (resolvedMessage) { - const auto peer = resolvedMessage->history()->peer; - _colorIndexPlusOne = (!holder->out() - && (peer->isMegagroup() || peer->isChat()) - && resolvedMessage->from()->isUser()) - ? (resolvedMessage->from()->colorIndex() + 1) - : uint8(); - } else if (!resolvedStory) { + _colorIndexPlusOne = resolvedMessage->computeColorIndex() + 1; + } else if (resolvedStory) { + _colorIndexPlusOne = resolvedStory->peer()->colorIndex() + 1; + } else { _unavailable = 1; } @@ -680,14 +681,15 @@ void HistoryMessageReply::paint( const auto rect = QRect(x, y, w, _height); const auto hasQuote = !_fields.quote.empty(); const auto selected = context.selected(); + const auto colorIndexPlusOne = context.outbg ? 0 : _colorIndexPlusOne; const auto cache = !inBubble ? (hasQuote ? st->serviceQuoteCache() : st->serviceReplyCache()).get() - : _colorIndexPlusOne + : colorIndexPlusOne ? (hasQuote - ? st->coloredQuoteCache(selected, _colorIndexPlusOne - 1) - : st->coloredReplyCache(selected, _colorIndexPlusOne - 1)).get() + ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) + : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() : (hasQuote ? stm->quoteCache : stm->replyCache).get(); const auto "eSt = hasQuote ? st::messageTextStyle.blockquote @@ -762,10 +764,10 @@ void HistoryMessageReply::paint( w -= textLeft + st::historyReplyPadding.right(); p.setPen(!inBubble ? st->msgImgReplyBarColor() - : _colorIndexPlusOne + : colorIndexPlusOne ? HistoryView::FromNameFg( context, - _colorIndexPlusOne - 1) + colorIndexPlusOne - 1) : stm->msgServiceFg); _name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); if (originalVia && w > _name.maxWidth() + st::msgServiceFont->spacew) { @@ -782,8 +784,8 @@ void HistoryMessageReply::paint( y + st::historyReplyPadding.top() + st::msgServiceNameFont->height); auto replyToTextPalette = &(!inBubble ? st->imgReplyTextPalette() - : _colorIndexPlusOne - ? st->coloredTextPalette(selected, _colorIndexPlusOne - 1) + : colorIndexPlusOne + ? st->coloredTextPalette(selected, colorIndexPlusOne - 1) : stm->replyTextPalette); if (_fields.storyId) { st::dialogsMiniReplyStory.icon.icon.paint( diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index c632b5b07..507fa46da 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -85,7 +85,10 @@ struct HistoryMessageEdited : public RuntimeComponent colorIndex = {}); QString name; QString firstName; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 384c8bae6..f9a82fe0a 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1330,12 +1330,9 @@ void Message::paintFromName( const auto from = item->displayFrom(); const auto info = from ? nullptr : item->hiddenSenderInfo(); Assert(from || info); - const auto service = (context.outbg || item->isPost()); const auto st = context.st; - const auto nameFg = !service + const auto nameFg = !context.outbg ? FromNameFg(context, from ? from->colorIndex() : info->colorIndex) - : item->isSponsored() - ? st->boxTextFgGood() : stm->msgServiceFg; const auto nameText = [&] { if (from) { @@ -3021,9 +3018,13 @@ void Message::updateViewButtonExistence() { return; } auto repainter = [=] { repaint(); }; + const auto index = item->computeColorIndex(); _viewButton = sponsored - ? std::make_unique(sponsored, std::move(repainter)) - : std::make_unique(media, std::move(repainter)); + ? std::make_unique( + sponsored, + index, + std::move(repainter)) + : std::make_unique(media, index, std::move(repainter)); } void Message::initLogEntryOriginal() { diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index 0ba4f8f2c..448ec0624 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -52,85 +52,31 @@ inline auto SponsoredPhrase(SponsoredType type) { return Ui::Text::Upper(phrase(tr::now)); } -inline auto WebPageToPhrase(not_null webpage) { - const auto type = webpage->type; - return Ui::Text::Upper((type == WebPageType::Theme) - ? tr::lng_view_button_theme(tr::now) - : (type == WebPageType::Story) - ? tr::lng_view_button_story(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::GroupWithRequest - || type == WebPageType::ChannelWithRequest) - ? tr::lng_view_button_request_join(tr::now) - : (type == WebPageType::ChannelBoost) - ? tr::lng_view_button_boost(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) - : (type == WebPageType::BotApp) - ? tr::lng_view_button_bot_app(tr::now) - : QString()); -} - [[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler( not_null media) { - if (const auto giveaway = media->giveaway()) { - const auto peer = media->parent()->history()->peer; - const auto messageId = media->parent()->id; - if (media->parent()->isSending() || media->parent()->hasFailed()) { - return nullptr; - } - const auto info = *giveaway; - return std::make_shared([=]( - ClickContext context) { - const auto my = context.other.value(); - const auto controller = my.sessionWindow.get(); - if (!controller) { - return; - } - ResolveGiveawayInfo(controller, peer, messageId, info); - }); + const auto giveaway = media->giveaway(); + Assert(giveaway != nullptr); + const auto peer = media->parent()->history()->peer; + const auto messageId = media->parent()->id; + if (media->parent()->isSending() || media->parent()->hasFailed()) { + return nullptr; } - const auto webpage = media->webpage(); - Assert(webpage != nullptr); - - const auto url = webpage->url; - const auto type = webpage->type; - return std::make_shared([=](ClickContext context) { + const auto info = *giveaway; + return std::make_shared([=]( + ClickContext context) { const auto my = context.other.value(); - if (const auto controller = my.sessionWindow.get()) { - if (type == WebPageType::BotApp) { - // Bot Web Apps always show confirmation on hidden urls. - // - // But from the dedicated "Open App" button we don't want - // to request users confirmation on non-first app opening. - UrlClickHandler::Open(url, context.other); - } else { - HiddenUrlClickHandler::Open(url, context.other); - } + const auto controller = my.sessionWindow.get(); + if (!controller) { + return; } + ResolveGiveawayInfo(controller, peer, messageId, info); }); } [[nodiscard]] QString MakeMediaButtonText(not_null media) { - if (const auto giveaway = media->giveaway()) { - return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now)); - } - const auto webpage = media->webpage(); - Assert(webpage != nullptr); - return WebPageToPhrase(webpage); + const auto giveaway = media->giveaway(); + Assert(giveaway != nullptr); + return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now)); } [[nodiscard]] ClickHandlerPtr SponsoredLink( @@ -178,8 +124,12 @@ inline auto WebPageToPhrase(not_null webpage) { struct ViewButton::Inner { Inner( not_null sponsored, + uint8 colorIndex, + Fn updateCallback); + Inner( + not_null media, + uint8 colorIndex, Fn updateCallback); - Inner(not_null media, Fn updateCallback); void updateMask(int height); void toggleRipple(bool pressed); @@ -187,59 +137,40 @@ struct ViewButton::Inner { const style::margins &margins; const ClickHandlerPtr link; const Fn updateCallback; - bool belowInfo = true; - bool externalLink = false; - int lastWidth = 0; + uint32 lastWidth : 24 = 0; + uint32 colorIndex : 6 = 0; + uint32 aboveInfo : 1 = 0; + uint32 externalLink : 1 = 0; QPoint lastPoint; std::unique_ptr ripple; Ui::Text::String text; }; bool ViewButton::MediaHasViewButton(not_null media) { - return media->webpage() - ? MediaHasViewButton(media->webpage()) - : (media->giveaway() != nullptr); -} - -bool ViewButton::MediaHasViewButton( - not_null webpage) { - const auto type = webpage->type; - return (type == WebPageType::Message) - || (type == WebPageType::Group) - || (type == WebPageType::Channel) - || (type == WebPageType::ChannelBoost) - // || (type == WebPageType::Bot) - || (type == WebPageType::User) - || (type == WebPageType::VoiceChat) - || (type == WebPageType::Livestream) - || (type == WebPageType::BotApp) - || ((type == WebPageType::Theme) - && webpage->document - && webpage->document->isTheme()) - || ((type == WebPageType::Story) - && (webpage->photo || webpage->document)) - || ((type == WebPageType::WallPaper) - && webpage->document - && webpage->document->isWallPaper()); + return (media->giveaway() != nullptr); } ViewButton::Inner::Inner( not_null sponsored, + uint8 colorIndex, Fn updateCallback) : margins(st::historyViewButtonMargins) , link(SponsoredLink(sponsored)) , updateCallback(std::move(updateCallback)) -, externalLink(sponsored->type == SponsoredType::ExternalLink) +, colorIndex(colorIndex) +, externalLink((sponsored->type == SponsoredType::ExternalLink) ? 1 : 0) , text(st::historyViewButtonTextStyle, SponsoredPhrase(sponsored->type)) { } ViewButton::Inner::Inner( not_null media, + uint8 colorIndex, Fn updateCallback) : margins(st::historyViewButtonMargins) , link(MakeMediaButtonClickHandler(media)) , updateCallback(std::move(updateCallback)) -, belowInfo(false) +, colorIndex(colorIndex) +, aboveInfo(1) , text(st::historyViewButtonTextStyle, MakeMediaButtonText(media)) { } @@ -264,14 +195,22 @@ void ViewButton::Inner::toggleRipple(bool pressed) { ViewButton::ViewButton( not_null sponsored, + uint8 colorIndex, Fn updateCallback) -: _inner(std::make_unique(sponsored, std::move(updateCallback))) { +: _inner(std::make_unique( + sponsored, + colorIndex, + std::move(updateCallback))) { } ViewButton::ViewButton( not_null media, + uint8 colorIndex, Fn updateCallback) -: _inner(std::make_unique(media, std::move(updateCallback))) { +: _inner(std::make_unique( + media, + colorIndex, + std::move(updateCallback))) { } ViewButton::~ViewButton() { @@ -286,7 +225,7 @@ int ViewButton::height() const { } bool ViewButton::belowMessageInfo() const { - return _inner->belowInfo; + return !_inner->aboveInfo; } void ViewButton::draw( @@ -295,45 +234,40 @@ void ViewButton::draw( const Ui::ChatPaintContext &context) { const auto stm = context.messageStyle(); + const auto selected = context.selected(); + const auto cache = context.outbg + ? stm->replyCache.get() + : context.st->coloredReplyCache(selected, _inner->colorIndex).get(); + const auto radius = st::historyPagePreview.radius; + 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); + _inner->ripple->paint(p, r.left(), r.top(), r.width(), &cache->bg); } - p.save(); - { - PainterHighQualityEnabler hq(p); - auto pen = stm->fwdTextPalette.linkFg->p; - pen.setWidth(st::lineWidth); - p.setPen(pen); - p.setBrush(Qt::NoBrush); - const auto half = st::lineWidth / 2.; - const auto rf = QRectF(r).marginsRemoved({ half, half, half, half }); - p.drawRoundedRect(rf, st::roundRadiusLarge, st::roundRadiusLarge); + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(cache->bg); + p.drawRoundedRect(r, radius, radius); - _inner->text.drawElided( + p.setPen(cache->outline); + _inner->text.drawElided( + p, + r.left(), + r.top() + (r.height() - _inner->text.minHeight()) / 2, + r.width(), + 1, + style::al_top); + + if (_inner->externalLink) { + const auto &icon = st::msgBotKbUrlIcon; + const auto padding = st::msgBotKbIconPadding; + icon.paint( p, - r.left(), - r.top() + (r.height() - _inner->text.minHeight()) / 2, + r.left() + r.width() - icon.width() - padding, + r.top() + padding, r.width(), - 1, - style::al_center); - - if (_inner->externalLink) { - const auto &icon = st::msgBotKbUrlIcon; - const auto padding = st::msgBotKbIconPadding; - icon.paint( - p, - r.left() + r.width() - icon.width() - padding, - r.top() + padding, - r.width(), - stm->fwdTextPalette.linkFg->c); - } + cache->outline); } - p.restore(); if (_inner->lastWidth != r.width()) { _inner->lastWidth = r.width(); resized(); diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.h b/Telegram/SourceFiles/history/view/history_view_view_button.h index d9b45b7f8..262f00dc0 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.h +++ b/Telegram/SourceFiles/history/view/history_view_view_button.h @@ -25,14 +25,16 @@ class ViewButton { public: ViewButton( not_null sponsored, + uint8 colorIndex, + Fn updateCallback); + ViewButton( + not_null media, + uint8 colorIndex, Fn updateCallback); - ViewButton(not_null media, Fn updateCallback); ~ViewButton(); [[nodiscard]] static bool MediaHasViewButton( not_null media); - [[nodiscard]] static bool MediaHasViewButton( - not_null webpage); [[nodiscard]] int height() const; [[nodiscard]] bool belowMessageInfo() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index 8713011c0..80204eb5d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -34,6 +34,7 @@ Game::Game( : Media(parent) , _st(st::historyPagePreview) , _data(data) +, _colorIndex(parent->data()->computeColorIndex()) , _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) { if (!consumed.text.isEmpty()) { @@ -48,13 +49,6 @@ Game::Game( context); } history()->owner().registerGameView(_data, _parent); - - const auto from = parent->data()->displayFrom(); - const auto info = from ? nullptr : parent->data()->hiddenSenderInfo(); - Assert(from || info); - _colorIndexPlusOne = !parent->data()->isPost() - ? ((from ? from->colorIndex() : info->colorIndex) + 1) - : 0; } QSize Game::countOptimalSize() { @@ -226,25 +220,36 @@ void Game::draw(Painter &p, const PaintContext &context) const { auto paintw = inner.width(); const auto selected = context.selected(); - const auto useColorIndex = context.outbg ? 0 : _colorIndexPlusOne; - const auto cache = useColorIndex - ? st->coloredReplyCache(selected, useColorIndex - 1).get() - : stm->replyCache.get(); + const auto cache = context.outbg + ? stm->replyCache.get() + : st->coloredReplyCache(selected, _colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); auto lineHeight = UnitedLineHeight(); if (_titleLines) { p.setPen(cache->outline); - p.setTextPalette(useColorIndex - ? st->coloredTextPalette(selected, useColorIndex - 1) - : stm->semiboldPalette); + p.setTextPalette(context.outbg + ? stm->semiboldPalette + : st->coloredTextPalette(selected, _colorIndex)); auto endskip = 0; if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _title.drawLeftElided(p, inner.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, context.selection); + _title.drawLeftElided( + p, + inner.left(), + tshift, + paintw, + width(), + _titleLines, + style::al_left, + 0, + -1, + endskip, + false, + context.selection); tshift += _titleLines * lineHeight; p.setTextPalette(stm->textPalette); diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.h b/Telegram/SourceFiles/history/view/media/history_view_game.h index 4cd5c613f..d942064fd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.h +++ b/Telegram/SourceFiles/history/view/media/history_view_game.h @@ -105,8 +105,8 @@ private: int _gameTagWidth = 0; int _descriptionLines = 0; - int _titleLines : 24 = 0; - int _colorIndexPlusOne : 8 = 0; + uint32 _titleLines : 24 = 0; + uint32 _colorIndex : 8 = 0; Ui::Text::String _title; Ui::Text::String _description; 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 799fe826a..268d216bc 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/chat/chat_style.h" #include "ui/cached_round_corners.h" +#include "ui/effects/ripple_animation.h" #include "ui/painter.h" #include "ui/power_saving.h" #include "data/data_session.h" @@ -84,6 +85,38 @@ std::vector> PrepareCollageMedia( return result; } +[[nodiscard]] QString PageToPhrase(not_null webpage) { + const auto type = webpage->type; + return Ui::Text::Upper((type == WebPageType::Theme) + ? tr::lng_view_button_theme(tr::now) + : (type == WebPageType::Story) + ? tr::lng_view_button_story(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::GroupWithRequest + || type == WebPageType::ChannelWithRequest) + ? tr::lng_view_button_request_join(tr::now) + : (type == WebPageType::ChannelBoost) + ? tr::lng_view_button_boost(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) + : (type == WebPageType::BotApp) + ? tr::lng_view_button_bot_app(tr::now) + : QString()); +} + } // namespace WebPage::WebPage( @@ -92,23 +125,47 @@ WebPage::WebPage( : Media(parent) , _st(st::historyPagePreview) , _data(data) +, _colorIndex(parent->data()->computeColorIndex()) , _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()) { history()->owner().registerWebPageView(_data, _parent); +} - const auto from = parent->data()->displayFrom(); - const auto info = from ? nullptr : parent->data()->hiddenSenderInfo(); - Assert(from || info); - _colorIndexPlusOne = !parent->data()->isPost() - ? ((from ? from->colorIndex() : info->colorIndex) + 1) - : 0; +bool WebPage::HasButton(not_null webpage) { + const auto type = webpage->type; + return (type == WebPageType::Message) + || (type == WebPageType::Group) + || (type == WebPageType::Channel) + || (type == WebPageType::ChannelBoost) + // || (type == WebPageType::Bot) + || (type == WebPageType::User) + || (type == WebPageType::VoiceChat) + || (type == WebPageType::Livestream) + || (type == WebPageType::BotApp) + || ((type == WebPageType::Theme) + && webpage->document + && webpage->document->isTheme()) + || ((type == WebPageType::Story) + && (webpage->photo || webpage->document)) + || ((type == WebPageType::WallPaper) + && webpage->document + && webpage->document->isWallPaper()); } QSize WebPage::countOptimalSize() { if (_data->pendingTill) { return { 0, 0 }; } + + // Detect _openButtonWidth before counting paddings. + _openButton = QString(); + _openButtonWidth = 0; + if (HasButton(_data)) { + _openButton = PageToPhrase(_data); + _openButtonWidth = st::semiboldFont->width(_openButton); + } + const auto padding = inBubblePadding() + innerMargin(); const auto versionChanged = (_dataVersion != _data->version); if (versionChanged) { @@ -127,6 +184,13 @@ QSize WebPage::countOptimalSize() { if (!_openl && !_data->url.isEmpty()) { const auto previewOfHiddenUrl = [&] { + if (_data->type == WebPageType::BotApp) { + // Bot Web Apps always show confirmation on hidden urls. + // + // But from the dedicated "Open App" button we don't want + // to request users confirmation on non-first app opening. + return false; + } const auto simplify = [](const QString &url) { auto result = url.toLower(); if (result.endsWith('/')) { @@ -175,7 +239,7 @@ QSize WebPage::countOptimalSize() { ? _data->author : _data->title); if (!_collage.empty()) { - _asArticle = false; + _asArticle = 0; } else if (!_data->document && _data->photo && _data->type != WebPageType::Photo @@ -183,22 +247,22 @@ QSize WebPage::countOptimalSize() { && _data->type != WebPageType::Story && _data->type != WebPageType::Video) { if (_data->type == WebPageType::Profile) { - _asArticle = true; + _asArticle = 1; } else if (_data->siteName == u"Twitter"_q || _data->siteName == u"Facebook"_q || _data->type == WebPageType::ArticleWithIV) { - _asArticle = false; + _asArticle = 0; } else { - _asArticle = true; + _asArticle = 1; } if (_asArticle && _data->description.text.isEmpty() && title.isEmpty() && _data->siteName.isEmpty()) { - _asArticle = false; + _asArticle = 0; } } else { - _asArticle = false; + _asArticle = 0; } // init attach @@ -211,8 +275,6 @@ QSize WebPage::countOptimalSize() { _data->url); } - _hasViewButton = ViewButton::MediaHasViewButton(_data); - // init strings if (_description.isEmpty() && !_data->description.text.isEmpty()) { auto text = _data->description; @@ -306,6 +368,10 @@ QSize WebPage::countOptimalSize() { _duration = Ui::FormatDurationText(_data->duration); _durationWidth = st::msgDateFont->width(_duration); } + if (_openButtonWidth) { + const auto &margins = st::historyPageButtonPadding; + maxWidth += margins.left() + _openButtonWidth + margins.right(); + } maxWidth += padding.left() + padding.right(); minHeight += padding.top() + padding.bottom(); @@ -472,13 +538,19 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); const auto selected = context.selected(); - const auto useColorIndex = context.outbg ? 0 : _colorIndexPlusOne; - const auto cache = useColorIndex - ? st->coloredReplyCache(selected, useColorIndex - 1).get() - : stm->replyCache.get(); + const auto cache = context.outbg + ? stm->replyCache.get() + : st->coloredReplyCache(selected, _colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); + if (_ripple) { + _ripple->paint(p, outer.x(), outer.y(), width(), &cache->bg); + if (_ripple->empty()) { + _ripple = nullptr; + } + } + auto lineHeight = UnitedLineHeight(); if (asArticle()) { ensurePhotoMediaCreated(); @@ -522,9 +594,9 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { } if (_siteNameLines) { p.setPen(cache->outline); - p.setTextPalette(useColorIndex - ? st->coloredTextPalette(selected, useColorIndex - 1) - : stm->semiboldPalette); + p.setTextPalette(context.outbg + ? stm->semiboldPalette + : st->coloredTextPalette(selected, _colorIndex)); auto endskip = 0; if (_siteName.hasSkipBlock()) { @@ -620,6 +692,21 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { p.drawTextLeft(st::msgPadding.left(), outer.y() + outer.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText); } } + + if (_openButtonWidth) { + p.setFont(st::semiboldFont); + p.setPen(cache->outline); + const auto end = inner.y() + inner.height() + _st.padding.bottom(); + const auto line = st::historyPageButtonLine; + auto color = cache->outline; + color.setAlphaF(color.alphaF() * 0.3); + p.fillRect(inner.x(), end, inner.width(), line, color); + const auto top = end + st::historyPageButtonPadding.top(); + p.drawText( + inner.x() + (inner.width() - _openButtonWidth) / 2, + top + st::semiboldFont->ascent, + _openButton); + } } bool WebPage::asArticle() const { @@ -715,9 +802,10 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { result.link = replaceAttachLink(result.link); } } - if (!result.link && inner.contains(point)) { + if (!result.link && outer.contains(point)) { result.link = _openl; } + _lastPoint = point - outer.topLeft(); result.symbol += symbolAdd; return result; @@ -773,13 +861,35 @@ TextSelection WebPage::adjustSelection(TextSelection selection, TextSelectType t return { siteNameSelection.from, fromDescriptionSelection(descriptionSelection).to }; } -void WebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { +void WebPage::clickHandlerActiveChanged( + const ClickHandlerPtr &p, + bool active) { if (_attach) { _attach->clickHandlerActiveChanged(p, active); } } -void WebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { +void WebPage::clickHandlerPressedChanged( + const ClickHandlerPtr &p, + bool pressed) { + if (p == _openl) { + if (pressed) { + if (!_ripple) { + const auto full = QRect(0, 0, width(), height()); + const auto outer = full.marginsRemoved(inBubblePadding()); + const auto owner = &parent()->history()->owner(); + _ripple = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RoundRectMask( + outer.size(), + _st.radius), + [=] { owner->requestViewRepaint(parent()); }); + } + _ripple->add(_lastPoint); + } else if (_ripple) { + _ripple->lastStop(); + } + } if (_attach) { _attach->clickHandlerPressedChanged(p, pressed); } @@ -811,6 +921,15 @@ QString WebPage::additionalInfoString() const { return _attach ? _attach->additionalInfoString() : QString(); } +bool WebPage::toggleSelectionByHandlerClick( + const ClickHandlerPtr &p) const { + return _attach && _attach->toggleSelectionByHandlerClick(p); +} + +bool WebPage::dragItemByHandler(const ClickHandlerPtr &p) const { + return _attach && _attach->dragItemByHandler(p); +} + TextForMimeData WebPage::selectedText(TextSelection selection) const { auto siteNameResult = _siteName.toTextForMimeData(selection); auto titleResult = _title.toTextForMimeData( @@ -846,7 +965,8 @@ QMargins WebPage::inBubblePadding() const { } QMargins WebPage::innerMargin() const { - return _st.padding; + const auto button = _openButtonWidth ? st::historyPageButtonHeight : 0; + return _st.padding + QMargins(0, 0, 0, button); } bool WebPage::isLogEntryOriginal() const { 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 bbccbd951..1e473207c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -14,6 +14,10 @@ class Media; class PhotoMedia; } // namespace Data +namespace Ui { +class RippleAnimation; +} // namespace Ui + namespace HistoryView { class WebPage : public Media { @@ -22,6 +26,8 @@ public: not_null parent, not_null data); + [[nodiscard]] static bool HasButton(not_null data); + void refreshParentId(not_null realParent) override; void draw(Painter &p, const PaintContext &context) const override; @@ -38,21 +44,21 @@ public: return _title.length() + _description.length(); } bool hasTextForCopy() const override { - return false; // we do not add _title and _description in FullSelection text copy. + // We do not add _title and _description in FullSelection text copy. + return false; } QString additionalInfoString() const override; - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return _attach && _attach->toggleSelectionByHandlerClick(p); - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return _attach && _attach->dragItemByHandler(p); - } + bool toggleSelectionByHandlerClick( + const ClickHandlerPtr &p) const override; + bool dragItemByHandler(const ClickHandlerPtr &p) const override; TextForMimeData selectedText(TextSelection selection) const override; - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + void clickHandlerActiveChanged( + const ClickHandlerPtr &p, bool active) override; + void clickHandlerPressedChanged( + const ClickHandlerPtr &p, bool pressed) override; bool isDisplayed() const override; PhotoData *getPhoto() const override { @@ -123,20 +129,25 @@ private: ClickHandlerPtr _openl; std::unique_ptr _attach; mutable std::shared_ptr _photoMedia; + mutable std::unique_ptr _ripple; - bool _asArticle = false; - bool _hasViewButton = false; int _dataVersion = -1; int _siteNameLines = 0; int _descriptionLines = 0; - int _titleLines : 24 = 0; - int _colorIndexPlusOne : 8 = 0; + uint32 _titleLines : 24 = 0; + uint32 _colorIndex : 7 = 0; + uint32 _asArticle : 1 = 0; - Ui::Text::String _siteName, _title, _description; + Ui::Text::String _siteName; + Ui::Text::String _title; + Ui::Text::String _description; + QString _openButton; QString _duration; + int _openButtonWidth = 0; int _durationWidth = 0; + mutable QPoint _lastPoint; int _pixw = 0; int _pixh = 0; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 0169149c0..6c2f61c27 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -624,10 +624,14 @@ historyPollOutChosenSelected: icon {{ "poll_select_check", historyFileOutIconFgS historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }}; historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }}; -historyViewButtonHeight: 42px; -historyViewButtonMargins: margins(13px, 5px, 13px, 5px); +historyViewButtonHeight: 48px; +historyViewButtonMargins: margins(10px, 5px, 10px, 10px); historyViewButtonTextStyle: semiboldTextStyle; +historyPageButtonLine: 1px; +historyPageButtonHeight: 36px; +historyPageButtonPadding: margins(13px, 8px, 13px, 8px); + historyCommentsButtonHeight: 40px; historyCommentsSkipLeft: 9px; historyCommentsSkipText: 10px;