Redesign webpage/giveaway/ads bottom button.

This commit is contained in:
John Preston 2023-10-18 13:57:03 +04:00
parent 16d18b437d
commit 3a84c6afdd
12 changed files with 315 additions and 222 deletions

View file

@ -1126,6 +1126,15 @@ PeerData *HistoryItem::displayFrom() const {
return author().get(); 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<HistoryView::Element> HistoryItem::createView( std::unique_ptr<HistoryView::Element> HistoryItem::createView(
not_null<HistoryView::ElementDelegate*> delegate, not_null<HistoryView::ElementDelegate*> delegate,
HistoryView::Element *replacing) { HistoryView::Element *replacing) {
@ -3253,7 +3262,8 @@ void HistoryItem::setSponsoredFrom(const Data::SponsoredFrom &from) {
const auto sponsored = Get<HistoryMessageSponsored>(); const auto sponsored = Get<HistoryMessageSponsored>();
sponsored->sender = std::make_unique<HiddenSenderInfo>( sponsored->sender = std::make_unique<HiddenSenderInfo>(
from.title, from.title,
false); false,
from.peer ? from.peer->colorIndex() : std::optional<uint8>());
sponsored->recommended = from.isRecommended; sponsored->recommended = from.isRecommended;
sponsored->isForceUserpicDisplay = from.isForceUserpicDisplay; sponsored->isForceUserpicDisplay = from.isForceUserpicDisplay;
if (from.userpic.location.valid()) { if (from.userpic.location.valid()) {

View file

@ -498,6 +498,7 @@ public:
[[nodiscard]] bool isDiscussionPost() const; [[nodiscard]] bool isDiscussionPost() const;
[[nodiscard]] HistoryItem *lookupDiscussionPostOriginal() const; [[nodiscard]] HistoryItem *lookupDiscussionPostOriginal() const;
[[nodiscard]] PeerData *displayFrom() const; [[nodiscard]] PeerData *displayFrom() const;
[[nodiscard]] uint8 computeColorIndex() const;
[[nodiscard]] std::unique_ptr<HistoryView::Element> createView( [[nodiscard]] std::unique_ptr<HistoryView::Element> createView(
not_null<HistoryView::ElementDelegate*> delegate, not_null<HistoryView::ElementDelegate*> delegate,

View file

@ -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<uint8> colorIndex)
: name(name) : name(name)
, colorIndex(Data::DecideColorIndex(Data::FakePeerIdForJustName(name))) , colorIndex(colorIndex.value_or(
Data::DecideColorIndex(Data::FakePeerIdForJustName(name))))
, emptyUserpic( , emptyUserpic(
Ui::EmptyUserpic::UserpicColor(colorIndex), Ui::EmptyUserpic::UserpicColor(this->colorIndex),
(external (external
? Ui::EmptyUserpic::ExternalName() ? Ui::EmptyUserpic::ExternalName()
: name)) { : name)) {
@ -399,13 +403,10 @@ bool HistoryMessageReply::updateData(
} }
if (resolvedMessage) { if (resolvedMessage) {
const auto peer = resolvedMessage->history()->peer; _colorIndexPlusOne = resolvedMessage->computeColorIndex() + 1;
_colorIndexPlusOne = (!holder->out() } else if (resolvedStory) {
&& (peer->isMegagroup() || peer->isChat()) _colorIndexPlusOne = resolvedStory->peer()->colorIndex() + 1;
&& resolvedMessage->from()->isUser()) } else {
? (resolvedMessage->from()->colorIndex() + 1)
: uint8();
} else if (!resolvedStory) {
_unavailable = 1; _unavailable = 1;
} }
@ -680,14 +681,15 @@ void HistoryMessageReply::paint(
const auto rect = QRect(x, y, w, _height); const auto rect = QRect(x, y, w, _height);
const auto hasQuote = !_fields.quote.empty(); const auto hasQuote = !_fields.quote.empty();
const auto selected = context.selected(); const auto selected = context.selected();
const auto colorIndexPlusOne = context.outbg ? 0 : _colorIndexPlusOne;
const auto cache = !inBubble const auto cache = !inBubble
? (hasQuote ? (hasQuote
? st->serviceQuoteCache() ? st->serviceQuoteCache()
: st->serviceReplyCache()).get() : st->serviceReplyCache()).get()
: _colorIndexPlusOne : colorIndexPlusOne
? (hasQuote ? (hasQuote
? st->coloredQuoteCache(selected, _colorIndexPlusOne - 1) ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1)
: st->coloredReplyCache(selected, _colorIndexPlusOne - 1)).get() : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get()
: (hasQuote ? stm->quoteCache : stm->replyCache).get(); : (hasQuote ? stm->quoteCache : stm->replyCache).get();
const auto &quoteSt = hasQuote const auto &quoteSt = hasQuote
? st::messageTextStyle.blockquote ? st::messageTextStyle.blockquote
@ -762,10 +764,10 @@ void HistoryMessageReply::paint(
w -= textLeft + st::historyReplyPadding.right(); w -= textLeft + st::historyReplyPadding.right();
p.setPen(!inBubble p.setPen(!inBubble
? st->msgImgReplyBarColor() ? st->msgImgReplyBarColor()
: _colorIndexPlusOne : colorIndexPlusOne
? HistoryView::FromNameFg( ? HistoryView::FromNameFg(
context, context,
_colorIndexPlusOne - 1) colorIndexPlusOne - 1)
: stm->msgServiceFg); : stm->msgServiceFg);
_name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); _name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft);
if (originalVia && w > _name.maxWidth() + st::msgServiceFont->spacew) { if (originalVia && w > _name.maxWidth() + st::msgServiceFont->spacew) {
@ -782,8 +784,8 @@ void HistoryMessageReply::paint(
y + st::historyReplyPadding.top() + st::msgServiceNameFont->height); y + st::historyReplyPadding.top() + st::msgServiceNameFont->height);
auto replyToTextPalette = &(!inBubble auto replyToTextPalette = &(!inBubble
? st->imgReplyTextPalette() ? st->imgReplyTextPalette()
: _colorIndexPlusOne : colorIndexPlusOne
? st->coloredTextPalette(selected, _colorIndexPlusOne - 1) ? st->coloredTextPalette(selected, colorIndexPlusOne - 1)
: stm->replyTextPalette); : stm->replyTextPalette);
if (_fields.storyId) { if (_fields.storyId) {
st::dialogsMiniReplyStory.icon.icon.paint( st::dialogsMiniReplyStory.icon.icon.paint(

View file

@ -85,7 +85,10 @@ struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited, Hist
class HiddenSenderInfo { class HiddenSenderInfo {
public: public:
HiddenSenderInfo(const QString &name, bool external); HiddenSenderInfo(
const QString &name,
bool external,
std::optional<uint8> colorIndex = {});
QString name; QString name;
QString firstName; QString firstName;

View file

@ -1330,12 +1330,9 @@ void Message::paintFromName(
const auto from = item->displayFrom(); const auto from = item->displayFrom();
const auto info = from ? nullptr : item->hiddenSenderInfo(); const auto info = from ? nullptr : item->hiddenSenderInfo();
Assert(from || info); Assert(from || info);
const auto service = (context.outbg || item->isPost());
const auto st = context.st; const auto st = context.st;
const auto nameFg = !service const auto nameFg = !context.outbg
? FromNameFg(context, from ? from->colorIndex() : info->colorIndex) ? FromNameFg(context, from ? from->colorIndex() : info->colorIndex)
: item->isSponsored()
? st->boxTextFgGood()
: stm->msgServiceFg; : stm->msgServiceFg;
const auto nameText = [&] { const auto nameText = [&] {
if (from) { if (from) {
@ -3021,9 +3018,13 @@ void Message::updateViewButtonExistence() {
return; return;
} }
auto repainter = [=] { repaint(); }; auto repainter = [=] { repaint(); };
const auto index = item->computeColorIndex();
_viewButton = sponsored _viewButton = sponsored
? std::make_unique<ViewButton>(sponsored, std::move(repainter)) ? std::make_unique<ViewButton>(
: std::make_unique<ViewButton>(media, std::move(repainter)); sponsored,
index,
std::move(repainter))
: std::make_unique<ViewButton>(media, index, std::move(repainter));
} }
void Message::initLogEntryOriginal() { void Message::initLogEntryOriginal() {

View file

@ -52,85 +52,31 @@ inline auto SponsoredPhrase(SponsoredType type) {
return Ui::Text::Upper(phrase(tr::now)); return Ui::Text::Upper(phrase(tr::now));
} }
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::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( [[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler(
not_null<Data::Media*> media) { not_null<Data::Media*> media) {
if (const auto giveaway = media->giveaway()) { const auto giveaway = media->giveaway();
const auto peer = media->parent()->history()->peer; Assert(giveaway != nullptr);
const auto messageId = media->parent()->id; const auto peer = media->parent()->history()->peer;
if (media->parent()->isSending() || media->parent()->hasFailed()) { const auto messageId = media->parent()->id;
return nullptr; if (media->parent()->isSending() || media->parent()->hasFailed()) {
} return nullptr;
const auto info = *giveaway;
return std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
if (!controller) {
return;
}
ResolveGiveawayInfo(controller, peer, messageId, info);
});
} }
const auto webpage = media->webpage(); const auto info = *giveaway;
Assert(webpage != nullptr); return std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto url = webpage->url;
const auto type = webpage->type;
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) { const auto controller = my.sessionWindow.get();
if (type == WebPageType::BotApp) { if (!controller) {
// Bot Web Apps always show confirmation on hidden urls. return;
//
// 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);
}
} }
ResolveGiveawayInfo(controller, peer, messageId, info);
}); });
} }
[[nodiscard]] QString MakeMediaButtonText(not_null<Data::Media*> media) { [[nodiscard]] QString MakeMediaButtonText(not_null<Data::Media*> media) {
if (const auto giveaway = media->giveaway()) { const auto giveaway = media->giveaway();
return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now)); Assert(giveaway != nullptr);
} return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now));
const auto webpage = media->webpage();
Assert(webpage != nullptr);
return WebPageToPhrase(webpage);
} }
[[nodiscard]] ClickHandlerPtr SponsoredLink( [[nodiscard]] ClickHandlerPtr SponsoredLink(
@ -178,8 +124,12 @@ inline auto WebPageToPhrase(not_null<WebPageData*> webpage) {
struct ViewButton::Inner { struct ViewButton::Inner {
Inner( Inner(
not_null<HistoryMessageSponsored*> sponsored, not_null<HistoryMessageSponsored*> sponsored,
uint8 colorIndex,
Fn<void()> updateCallback);
Inner(
not_null<Data::Media*> media,
uint8 colorIndex,
Fn<void()> updateCallback); 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);
@ -187,59 +137,40 @@ struct ViewButton::Inner {
const style::margins &margins; const style::margins &margins;
const ClickHandlerPtr link; const ClickHandlerPtr link;
const Fn<void()> updateCallback; const Fn<void()> updateCallback;
bool belowInfo = true; uint32 lastWidth : 24 = 0;
bool externalLink = false; uint32 colorIndex : 6 = 0;
int lastWidth = 0; uint32 aboveInfo : 1 = 0;
uint32 externalLink : 1 = 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) { bool ViewButton::MediaHasViewButton(not_null<Data::Media*> media) {
return media->webpage() return (media->giveaway() != nullptr);
? MediaHasViewButton(media->webpage())
: (media->giveaway() != nullptr);
}
bool ViewButton::MediaHasViewButton(
not_null<WebPageData*> 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());
} }
ViewButton::Inner::Inner( ViewButton::Inner::Inner(
not_null<HistoryMessageSponsored*> sponsored, not_null<HistoryMessageSponsored*> sponsored,
uint8 colorIndex,
Fn<void()> updateCallback) Fn<void()> updateCallback)
: margins(st::historyViewButtonMargins) : margins(st::historyViewButtonMargins)
, link(SponsoredLink(sponsored)) , link(SponsoredLink(sponsored))
, updateCallback(std::move(updateCallback)) , updateCallback(std::move(updateCallback))
, externalLink(sponsored->type == SponsoredType::ExternalLink) , colorIndex(colorIndex)
, externalLink((sponsored->type == SponsoredType::ExternalLink) ? 1 : 0)
, text(st::historyViewButtonTextStyle, SponsoredPhrase(sponsored->type)) { , text(st::historyViewButtonTextStyle, SponsoredPhrase(sponsored->type)) {
} }
ViewButton::Inner::Inner( ViewButton::Inner::Inner(
not_null<Data::Media*> media, not_null<Data::Media*> media,
uint8 colorIndex,
Fn<void()> updateCallback) Fn<void()> updateCallback)
: margins(st::historyViewButtonMargins) : margins(st::historyViewButtonMargins)
, link(MakeMediaButtonClickHandler(media)) , link(MakeMediaButtonClickHandler(media))
, updateCallback(std::move(updateCallback)) , updateCallback(std::move(updateCallback))
, belowInfo(false) , colorIndex(colorIndex)
, aboveInfo(1)
, text(st::historyViewButtonTextStyle, MakeMediaButtonText(media)) { , text(st::historyViewButtonTextStyle, MakeMediaButtonText(media)) {
} }
@ -264,14 +195,22 @@ void ViewButton::Inner::toggleRipple(bool pressed) {
ViewButton::ViewButton( ViewButton::ViewButton(
not_null<HistoryMessageSponsored*> sponsored, not_null<HistoryMessageSponsored*> sponsored,
uint8 colorIndex,
Fn<void()> updateCallback) Fn<void()> updateCallback)
: _inner(std::make_unique<Inner>(sponsored, std::move(updateCallback))) { : _inner(std::make_unique<Inner>(
sponsored,
colorIndex,
std::move(updateCallback))) {
} }
ViewButton::ViewButton( ViewButton::ViewButton(
not_null<Data::Media*> media, not_null<Data::Media*> media,
uint8 colorIndex,
Fn<void()> updateCallback) Fn<void()> updateCallback)
: _inner(std::make_unique<Inner>(media, std::move(updateCallback))) { : _inner(std::make_unique<Inner>(
media,
colorIndex,
std::move(updateCallback))) {
} }
ViewButton::~ViewButton() { ViewButton::~ViewButton() {
@ -286,7 +225,7 @@ int ViewButton::height() const {
} }
bool ViewButton::belowMessageInfo() const { bool ViewButton::belowMessageInfo() const {
return _inner->belowInfo; return !_inner->aboveInfo;
} }
void ViewButton::draw( void ViewButton::draw(
@ -295,45 +234,40 @@ void ViewButton::draw(
const Ui::ChatPaintContext &context) { const Ui::ChatPaintContext &context) {
const auto stm = context.messageStyle(); 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()) { if (_inner->ripple && !_inner->ripple->empty()) {
const auto opacity = p.opacity(); _inner->ripple->paint(p, r.left(), r.top(), r.width(), &cache->bg);
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(Qt::NoPen);
PainterHighQualityEnabler hq(p); p.setBrush(cache->bg);
auto pen = stm->fwdTextPalette.linkFg->p; p.drawRoundedRect(r, radius, radius);
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);
_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, p,
r.left(), r.left() + r.width() - icon.width() - padding,
r.top() + (r.height() - _inner->text.minHeight()) / 2, r.top() + padding,
r.width(), r.width(),
1, cache->outline);
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);
}
} }
p.restore();
if (_inner->lastWidth != r.width()) { if (_inner->lastWidth != r.width()) {
_inner->lastWidth = r.width(); _inner->lastWidth = r.width();
resized(); resized();

View file

@ -25,14 +25,16 @@ class ViewButton {
public: public:
ViewButton( ViewButton(
not_null<HistoryMessageSponsored*> sponsored, not_null<HistoryMessageSponsored*> sponsored,
uint8 colorIndex,
Fn<void()> updateCallback);
ViewButton(
not_null<Data::Media*> media,
uint8 colorIndex,
Fn<void()> updateCallback); Fn<void()> updateCallback);
ViewButton(not_null<Data::Media*> media, Fn<void()> updateCallback);
~ViewButton(); ~ViewButton();
[[nodiscard]] static bool MediaHasViewButton( [[nodiscard]] static bool MediaHasViewButton(
not_null<Data::Media*> media); not_null<Data::Media*> media);
[[nodiscard]] static bool MediaHasViewButton(
not_null<WebPageData*> webpage);
[[nodiscard]] int height() const; [[nodiscard]] int height() const;
[[nodiscard]] bool belowMessageInfo() const; [[nodiscard]] bool belowMessageInfo() const;

View file

@ -34,6 +34,7 @@ Game::Game(
: Media(parent) : Media(parent)
, _st(st::historyPagePreview) , _st(st::historyPagePreview)
, _data(data) , _data(data)
, _colorIndex(parent->data()->computeColorIndex())
, _title(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()) { , _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) {
if (!consumed.text.isEmpty()) { if (!consumed.text.isEmpty()) {
@ -48,13 +49,6 @@ Game::Game(
context); context);
} }
history()->owner().registerGameView(_data, _parent); 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() { QSize Game::countOptimalSize() {
@ -226,25 +220,36 @@ void Game::draw(Painter &p, const PaintContext &context) const {
auto paintw = inner.width(); auto paintw = inner.width();
const auto selected = context.selected(); const auto selected = context.selected();
const auto useColorIndex = context.outbg ? 0 : _colorIndexPlusOne; const auto cache = context.outbg
const auto cache = useColorIndex ? stm->replyCache.get()
? st->coloredReplyCache(selected, useColorIndex - 1).get() : st->coloredReplyCache(selected, _colorIndex).get();
: stm->replyCache.get();
Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::ValidateQuotePaintCache(*cache, _st);
Ui::Text::FillQuotePaint(p, outer, *cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st);
auto lineHeight = UnitedLineHeight(); auto lineHeight = UnitedLineHeight();
if (_titleLines) { if (_titleLines) {
p.setPen(cache->outline); p.setPen(cache->outline);
p.setTextPalette(useColorIndex p.setTextPalette(context.outbg
? st->coloredTextPalette(selected, useColorIndex - 1) ? stm->semiboldPalette
: stm->semiboldPalette); : st->coloredTextPalette(selected, _colorIndex));
auto endskip = 0; auto endskip = 0;
if (_title.hasSkipBlock()) { if (_title.hasSkipBlock()) {
endskip = _parent->skipBlockWidth(); 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; tshift += _titleLines * lineHeight;
p.setTextPalette(stm->textPalette); p.setTextPalette(stm->textPalette);

View file

@ -105,8 +105,8 @@ private:
int _gameTagWidth = 0; int _gameTagWidth = 0;
int _descriptionLines = 0; int _descriptionLines = 0;
int _titleLines : 24 = 0; uint32 _titleLines : 24 = 0;
int _colorIndexPlusOne : 8 = 0; uint32 _colorIndex : 8 = 0;
Ui::Text::String _title; Ui::Text::String _title;
Ui::Text::String _description; Ui::Text::String _description;

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/power_saving.h" #include "ui/power_saving.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -84,6 +85,38 @@ std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia(
return result; return result;
} }
[[nodiscard]] QString PageToPhrase(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::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 } // namespace
WebPage::WebPage( WebPage::WebPage(
@ -92,23 +125,47 @@ WebPage::WebPage(
: Media(parent) : Media(parent)
, _st(st::historyPagePreview) , _st(st::historyPagePreview)
, _data(data) , _data(data)
, _colorIndex(parent->data()->computeColorIndex())
, _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right())
, _title(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()) { , _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) {
history()->owner().registerWebPageView(_data, _parent); history()->owner().registerWebPageView(_data, _parent);
}
const auto from = parent->data()->displayFrom(); bool WebPage::HasButton(not_null<WebPageData*> webpage) {
const auto info = from ? nullptr : parent->data()->hiddenSenderInfo(); const auto type = webpage->type;
Assert(from || info); return (type == WebPageType::Message)
_colorIndexPlusOne = !parent->data()->isPost() || (type == WebPageType::Group)
? ((from ? from->colorIndex() : info->colorIndex) + 1) || (type == WebPageType::Channel)
: 0; || (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() { QSize WebPage::countOptimalSize() {
if (_data->pendingTill) { if (_data->pendingTill) {
return { 0, 0 }; 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 padding = inBubblePadding() + innerMargin();
const auto versionChanged = (_dataVersion != _data->version); const auto versionChanged = (_dataVersion != _data->version);
if (versionChanged) { if (versionChanged) {
@ -127,6 +184,13 @@ QSize WebPage::countOptimalSize() {
if (!_openl && !_data->url.isEmpty()) { if (!_openl && !_data->url.isEmpty()) {
const auto previewOfHiddenUrl = [&] { 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) { const auto simplify = [](const QString &url) {
auto result = url.toLower(); auto result = url.toLower();
if (result.endsWith('/')) { if (result.endsWith('/')) {
@ -175,7 +239,7 @@ QSize WebPage::countOptimalSize() {
? _data->author ? _data->author
: _data->title); : _data->title);
if (!_collage.empty()) { if (!_collage.empty()) {
_asArticle = false; _asArticle = 0;
} else if (!_data->document } else if (!_data->document
&& _data->photo && _data->photo
&& _data->type != WebPageType::Photo && _data->type != WebPageType::Photo
@ -183,22 +247,22 @@ QSize WebPage::countOptimalSize() {
&& _data->type != WebPageType::Story && _data->type != WebPageType::Story
&& _data->type != WebPageType::Video) { && _data->type != WebPageType::Video) {
if (_data->type == WebPageType::Profile) { if (_data->type == WebPageType::Profile) {
_asArticle = true; _asArticle = 1;
} else if (_data->siteName == u"Twitter"_q } else if (_data->siteName == u"Twitter"_q
|| _data->siteName == u"Facebook"_q || _data->siteName == u"Facebook"_q
|| _data->type == WebPageType::ArticleWithIV) { || _data->type == WebPageType::ArticleWithIV) {
_asArticle = false; _asArticle = 0;
} else { } else {
_asArticle = true; _asArticle = 1;
} }
if (_asArticle if (_asArticle
&& _data->description.text.isEmpty() && _data->description.text.isEmpty()
&& title.isEmpty() && title.isEmpty()
&& _data->siteName.isEmpty()) { && _data->siteName.isEmpty()) {
_asArticle = false; _asArticle = 0;
} }
} else { } else {
_asArticle = false; _asArticle = 0;
} }
// init attach // init attach
@ -211,8 +275,6 @@ QSize WebPage::countOptimalSize() {
_data->url); _data->url);
} }
_hasViewButton = ViewButton::MediaHasViewButton(_data);
// init strings // init strings
if (_description.isEmpty() && !_data->description.text.isEmpty()) { if (_description.isEmpty() && !_data->description.text.isEmpty()) {
auto text = _data->description; auto text = _data->description;
@ -306,6 +368,10 @@ QSize WebPage::countOptimalSize() {
_duration = Ui::FormatDurationText(_data->duration); _duration = Ui::FormatDurationText(_data->duration);
_durationWidth = st::msgDateFont->width(_duration); _durationWidth = st::msgDateFont->width(_duration);
} }
if (_openButtonWidth) {
const auto &margins = st::historyPageButtonPadding;
maxWidth += margins.left() + _openButtonWidth + margins.right();
}
maxWidth += padding.left() + padding.right(); maxWidth += padding.left() + padding.right();
minHeight += padding.top() + padding.bottom(); minHeight += padding.top() + padding.bottom();
@ -472,13 +538,19 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString();
const auto selected = context.selected(); const auto selected = context.selected();
const auto useColorIndex = context.outbg ? 0 : _colorIndexPlusOne; const auto cache = context.outbg
const auto cache = useColorIndex ? stm->replyCache.get()
? st->coloredReplyCache(selected, useColorIndex - 1).get() : st->coloredReplyCache(selected, _colorIndex).get();
: stm->replyCache.get();
Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::ValidateQuotePaintCache(*cache, _st);
Ui::Text::FillQuotePaint(p, outer, *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(); auto lineHeight = UnitedLineHeight();
if (asArticle()) { if (asArticle()) {
ensurePhotoMediaCreated(); ensurePhotoMediaCreated();
@ -522,9 +594,9 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
} }
if (_siteNameLines) { if (_siteNameLines) {
p.setPen(cache->outline); p.setPen(cache->outline);
p.setTextPalette(useColorIndex p.setTextPalette(context.outbg
? st->coloredTextPalette(selected, useColorIndex - 1) ? stm->semiboldPalette
: stm->semiboldPalette); : st->coloredTextPalette(selected, _colorIndex));
auto endskip = 0; auto endskip = 0;
if (_siteName.hasSkipBlock()) { 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); 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 { bool WebPage::asArticle() const {
@ -715,9 +802,10 @@ TextState WebPage::textState(QPoint point, StateRequest request) const {
result.link = replaceAttachLink(result.link); result.link = replaceAttachLink(result.link);
} }
} }
if (!result.link && inner.contains(point)) { if (!result.link && outer.contains(point)) {
result.link = _openl; result.link = _openl;
} }
_lastPoint = point - outer.topLeft();
result.symbol += symbolAdd; result.symbol += symbolAdd;
return result; return result;
@ -773,13 +861,35 @@ TextSelection WebPage::adjustSelection(TextSelection selection, TextSelectType t
return { siteNameSelection.from, fromDescriptionSelection(descriptionSelection).to }; return { siteNameSelection.from, fromDescriptionSelection(descriptionSelection).to };
} }
void WebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { void WebPage::clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) {
if (_attach) { if (_attach) {
_attach->clickHandlerActiveChanged(p, active); _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<Ui::RippleAnimation>(
st::defaultRippleAnimation,
Ui::RippleAnimation::RoundRectMask(
outer.size(),
_st.radius),
[=] { owner->requestViewRepaint(parent()); });
}
_ripple->add(_lastPoint);
} else if (_ripple) {
_ripple->lastStop();
}
}
if (_attach) { if (_attach) {
_attach->clickHandlerPressedChanged(p, pressed); _attach->clickHandlerPressedChanged(p, pressed);
} }
@ -811,6 +921,15 @@ QString WebPage::additionalInfoString() const {
return _attach ? _attach->additionalInfoString() : QString(); 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 { TextForMimeData WebPage::selectedText(TextSelection selection) const {
auto siteNameResult = _siteName.toTextForMimeData(selection); auto siteNameResult = _siteName.toTextForMimeData(selection);
auto titleResult = _title.toTextForMimeData( auto titleResult = _title.toTextForMimeData(
@ -846,7 +965,8 @@ QMargins WebPage::inBubblePadding() const {
} }
QMargins WebPage::innerMargin() 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 { bool WebPage::isLogEntryOriginal() const {

View file

@ -14,6 +14,10 @@ class Media;
class PhotoMedia; class PhotoMedia;
} // namespace Data } // namespace Data
namespace Ui {
class RippleAnimation;
} // namespace Ui
namespace HistoryView { namespace HistoryView {
class WebPage : public Media { class WebPage : public Media {
@ -22,6 +26,8 @@ public:
not_null<Element*> parent, not_null<Element*> parent,
not_null<WebPageData*> data); not_null<WebPageData*> data);
[[nodiscard]] static bool HasButton(not_null<WebPageData*> data);
void refreshParentId(not_null<HistoryItem*> realParent) override; void refreshParentId(not_null<HistoryItem*> realParent) override;
void draw(Painter &p, const PaintContext &context) const override; void draw(Painter &p, const PaintContext &context) const override;
@ -38,21 +44,21 @@ public:
return _title.length() + _description.length(); return _title.length() + _description.length();
} }
bool hasTextForCopy() const override { 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; QString additionalInfoString() const override;
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { bool toggleSelectionByHandlerClick(
return _attach && _attach->toggleSelectionByHandlerClick(p); const ClickHandlerPtr &p) const override;
} bool dragItemByHandler(const ClickHandlerPtr &p) const override;
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
return _attach && _attach->dragItemByHandler(p);
}
TextForMimeData selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerActiveChanged(
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(
const ClickHandlerPtr &p, bool pressed) override;
bool isDisplayed() const override; bool isDisplayed() const override;
PhotoData *getPhoto() const override { PhotoData *getPhoto() const override {
@ -123,20 +129,25 @@ private:
ClickHandlerPtr _openl; ClickHandlerPtr _openl;
std::unique_ptr<Media> _attach; std::unique_ptr<Media> _attach;
mutable std::shared_ptr<Data::PhotoMedia> _photoMedia; mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
mutable std::unique_ptr<Ui::RippleAnimation> _ripple;
bool _asArticle = false;
bool _hasViewButton = false;
int _dataVersion = -1; int _dataVersion = -1;
int _siteNameLines = 0; int _siteNameLines = 0;
int _descriptionLines = 0; int _descriptionLines = 0;
int _titleLines : 24 = 0; uint32 _titleLines : 24 = 0;
int _colorIndexPlusOne : 8 = 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; QString _duration;
int _openButtonWidth = 0;
int _durationWidth = 0; int _durationWidth = 0;
mutable QPoint _lastPoint;
int _pixw = 0; int _pixw = 0;
int _pixh = 0; int _pixh = 0;

View file

@ -624,10 +624,14 @@ historyPollOutChosenSelected: icon {{ "poll_select_check", historyFileOutIconFgS
historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }}; historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }};
historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }}; historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }};
historyViewButtonHeight: 42px; historyViewButtonHeight: 48px;
historyViewButtonMargins: margins(13px, 5px, 13px, 5px); historyViewButtonMargins: margins(10px, 5px, 10px, 10px);
historyViewButtonTextStyle: semiboldTextStyle; historyViewButtonTextStyle: semiboldTextStyle;
historyPageButtonLine: 1px;
historyPageButtonHeight: 36px;
historyPageButtonPadding: margins(13px, 8px, 13px, 8px);
historyCommentsButtonHeight: 40px; historyCommentsButtonHeight: 40px;
historyCommentsSkipLeft: 9px; historyCommentsSkipLeft: 9px;
historyCommentsSkipText: 10px; historyCommentsSkipText: 10px;