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

View File

@ -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<HistoryView::Element> createView(
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)
, 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 &quoteSt = 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(

View File

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

View File

@ -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<ViewButton>(sponsored, std::move(repainter))
: std::make_unique<ViewButton>(media, std::move(repainter));
? std::make_unique<ViewButton>(
sponsored,
index,
std::move(repainter))
: std::make_unique<ViewButton>(media, index, std::move(repainter));
}
void Message::initLogEntryOriginal() {

View File

@ -52,85 +52,31 @@ inline auto SponsoredPhrase(SponsoredType type) {
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(
not_null<Data::Media*> 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<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 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<LambdaClickHandler>([=](ClickContext context) {
const auto info = *giveaway;
return std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
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<Data::Media*> 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<WebPageData*> webpage) {
struct ViewButton::Inner {
Inner(
not_null<HistoryMessageSponsored*> sponsored,
uint8 colorIndex,
Fn<void()> updateCallback);
Inner(
not_null<Data::Media*> media,
uint8 colorIndex,
Fn<void()> updateCallback);
Inner(not_null<Data::Media*> media, Fn<void()> 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<void()> 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<Ui::RippleAnimation> ripple;
Ui::Text::String text;
};
bool ViewButton::MediaHasViewButton(not_null<Data::Media*> media) {
return media->webpage()
? 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());
return (media->giveaway() != nullptr);
}
ViewButton::Inner::Inner(
not_null<HistoryMessageSponsored*> sponsored,
uint8 colorIndex,
Fn<void()> 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<Data::Media*> media,
uint8 colorIndex,
Fn<void()> 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<HistoryMessageSponsored*> sponsored,
uint8 colorIndex,
Fn<void()> updateCallback)
: _inner(std::make_unique<Inner>(sponsored, std::move(updateCallback))) {
: _inner(std::make_unique<Inner>(
sponsored,
colorIndex,
std::move(updateCallback))) {
}
ViewButton::ViewButton(
not_null<Data::Media*> media,
uint8 colorIndex,
Fn<void()> updateCallback)
: _inner(std::make_unique<Inner>(media, std::move(updateCallback))) {
: _inner(std::make_unique<Inner>(
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();

View File

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

View File

@ -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);

View File

@ -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;

View File

@ -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<std::unique_ptr<Data::Media>> PrepareCollageMedia(
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
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<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());
}
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<Ui::RippleAnimation>(
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 {

View File

@ -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<Element*> parent,
not_null<WebPageData*> data);
[[nodiscard]] static bool HasButton(not_null<WebPageData*> data);
void refreshParentId(not_null<HistoryItem*> 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<Media> _attach;
mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
mutable std::unique_ptr<Ui::RippleAnimation> _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;

View File

@ -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;