Show toggle-able factcheck footer.

This commit is contained in:
John Preston 2024-05-23 13:07:03 +04:00
parent b299881bf8
commit 923a9ec6a8
9 changed files with 456 additions and 190 deletions

View file

@ -466,6 +466,8 @@ PRIVATE
data/business/data_business_info.h
data/business/data_shortcut_messages.cpp
data/business/data_shortcut_messages.h
data/components/factchecks.cpp
data/components/factchecks.h
data/components/recent_peers.cpp
data/components/recent_peers.h
data/components/scheduled_messages.cpp

View file

@ -3287,6 +3287,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_spoiler_effect" = "Hide with Spoiler";
"lng_context_disable_spoiler" = "Remove Spoiler";
"lng_context_add_factcheck" = "Add Fact Check";
"lng_factcheck_title" = "Fact Check";
"lng_factcheck_placeholder" = "Add Facts or Context";
"lng_factcheck_whats_this" = "what's this?";
"lng_factcheck_about" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
"lng_translate_show_original" = "Show Original";
"lng_translate_bar_to" = "Translate to {name}";

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/random.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "history/view/media/history_view_web_page.h"
#include "history/view/history_view_message.h"
#include "history/history.h"
@ -126,6 +127,7 @@ std::unique_ptr<HistoryView::WebPage> Factchecks::makeMedia(
base::RandomValue<WebPageId>(),
tr::lng_factcheck_title(tr::now),
factcheck->data.text);
factcheck->page->type = WebPageType::Factcheck;
}
return std::make_unique<HistoryView::WebPage>(
view,

View file

@ -54,6 +54,8 @@ enum class WebPageType : uint8 {
VoiceChat,
Livestream,
Factcheck,
};
[[nodiscard]] WebPageType ParseWebPageType(const MTPDwebPage &type);
[[nodiscard]] bool IgnoreIv(WebPageType type);

View file

@ -111,7 +111,8 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) {
| MTPDmessage::Flag::f_forwards
//| MTPDmessage::Flag::f_reactions
| MTPDmessage::Flag::f_restriction_reason
| MTPDmessage::Flag::f_ttl_period;
| MTPDmessage::Flag::f_ttl_period
| MTPDmessage::Flag::f_factcheck;
return MTP_message(
MTP_flags(data.vflags().v & ~removeFlags),
data.vid(),
@ -141,7 +142,7 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) {
MTPint(), // ttl_period
MTPint(), // quick_reply_shortcut_id
MTP_long(data.veffect().value_or_empty()),
data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck());
MTPFactCheck());
});
}

View file

@ -586,10 +586,11 @@ void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) {
}
if (bubble) {
auto entry = logEntryOriginal();
const auto check = factcheckBlock();
const auto entry = logEntryOriginal();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto inner = g;
@ -636,10 +637,11 @@ void Message::animateEffect(Ui::ReactionFlyAnimationArgs &&args) {
_bottomInfo.animateEffect(args.translated(-bottomRight), repainter);
};
if (bubble) {
auto entry = logEntryOriginal();
const auto entry = logEntryOriginal();
const auto check = factcheckBlock();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto inner = g;
@ -732,10 +734,11 @@ QRect Message::effectIconGeometry() const {
bottomRight - QPoint(size.width(), size.height()));
};
if (bubble) {
auto entry = logEntryOriginal();
const auto entry = logEntryOriginal();
const auto check = factcheckBlock();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto inner = g;
@ -806,14 +809,6 @@ QSize Message::performCountOptimalSize() {
Get<Factcheck>()->page = history()->session().factchecks().makeMedia(
this,
factcheck);
auto copy = data()->originalText();
if (!copy.text.contains("FACT CHECK")) {
copy.append("\n\nFACT CHECK!!\n\n").append(factcheck->data.text);
crl::on_main(this, [=] {
data()->setText(std::move(copy));
});
}
} else {
RemoveComponents(Factcheck::Bit());
}
@ -863,6 +858,7 @@ QSize Message::performCountOptimalSize() {
const auto forwarded = item->Get<HistoryMessageForwarded>();
const auto via = item->Get<HistoryMessageVia>();
const auto entry = logEntryOriginal();
const auto check = factcheckBlock();
if (forwarded) {
forwarded->create(via, item);
}
@ -872,13 +868,16 @@ QSize Message::performCountOptimalSize() {
mediaDisplayed = media->isDisplayed();
media->initDimensions();
}
if (check) {
check->initDimensions();
}
if (entry) {
entry->initDimensions();
}
// Entry page is always a bubble bottom.
const auto withVisibleText = hasVisibleText();
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
maxWidth = plainMaxWidth();
if (context() == Context::Replies && item->isDiscussionPost()) {
@ -918,6 +917,7 @@ QSize Message::performCountOptimalSize() {
minHeight += st::msgPadding.top();
if (mediaDisplayed) minHeight += st::mediaInBubbleSkip;
if (entry) minHeight += st::mediaInBubbleSkip;
if (check) minHeight += st::mediaInBubbleSkip;
}
if (mediaDisplayed) {
// Parts don't participate in maxWidth() in case of media message.
@ -1002,6 +1002,10 @@ QSize Message::performCountOptimalSize() {
+ st::msgPadding.right();
accumulate_max(maxWidth, replyw);
}
if (check) {
accumulate_max(maxWidth, check->maxWidth());
minHeight += check->minHeight();
}
if (entry) {
accumulate_max(maxWidth, entry->maxWidth());
minHeight += entry->minHeight();
@ -1121,11 +1125,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
return;
}
auto entry = logEntryOriginal();
const auto entry = logEntryOriginal();
const auto check = factcheckBlock();
auto mediaDisplayed = media && media->isDisplayed();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
const auto displayInfo = needInfoDisplay();
@ -1150,6 +1155,9 @@ void Message::draw(Painter &p, const PaintContext &context) const {
if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) {
localMediaBottom -= st::msgPadding.bottom();
}
if (check) {
localMediaBottom -= check->height();
}
if (entry) {
localMediaBottom -= entry->height();
}
@ -1299,6 +1307,9 @@ void Message::draw(Painter &p, const PaintContext &context) const {
if (entry) {
trect.setHeight(trect.height() - entry->height());
}
if (check) {
trect.setHeight(trect.height() - check->height());
}
if (displayInfo) {
trect.setHeight(trect.height()
- (_bottomInfo.height() - st::msgDateFont->height));
@ -1358,6 +1369,19 @@ void Message::draw(Painter &p, const PaintContext &context) const {
}
}
}
if (check) {
auto checkLeft = inner.left();
auto checkTop = trect.y() + trect.height();
p.translate(checkLeft, checkTop);
auto checkContext = context.translated(checkLeft, -checkTop);
checkContext.selection = skipTextSelection(context.selection);
if (mediaDisplayed) {
checkContext.selection = media->skipSelection(
checkContext.selection);
}
check->draw(p, checkContext);
p.translate(-checkLeft, -checkTop);
}
if (entry) {
auto entryLeft = inner.left();
auto entryTop = trect.y() + trect.height();
@ -1924,10 +1948,11 @@ PointState Message::pointState(QPoint point) const {
}
if (const auto mediaDisplayed = media && media->isDisplayed()) {
// Hack for grouped media point state.
auto entry = logEntryOriginal();
const auto entry = logEntryOriginal();
const auto check = factcheckBlock();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
if (item->repliesAreComments() || item->externalReply()) {
g.setHeight(g.height() - st::historyCommentsButtonHeight);
@ -1959,6 +1984,10 @@ PointState Message::pointState(QPoint point) const {
// if (getStateReplyInfo(point, trect, &result)) return result;
// if (getStateViaBotIdInfo(point, trect, &result)) return result;
//}
if (check) {
auto checkHeight = check->height();
trect.setHeight(trect.height() - checkHeight);
}
if (entry) {
auto entryHeight = entry->height();
trect.setHeight(trect.height() - entryHeight);
@ -1995,6 +2024,9 @@ void Message::clickHandlerPressedChanged(
}
}
Element::clickHandlerPressedChanged(handler, pressed);
if (const auto check = factcheckBlock()) {
check->clickHandlerPressedChanged(handler, pressed);
}
if (!handler) {
return;
} else if (_rightAction && (handler == _rightAction->link)) {
@ -2306,10 +2338,11 @@ TextState Message::textState(
if (bubble) {
const auto inBubble = g.contains(point);
auto entry = logEntryOriginal();
const auto check = factcheckBlock();
const auto entry = logEntryOriginal();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto inner = g;
@ -2393,9 +2426,23 @@ TextState Message::textState(
+ visibleMediaTextLength();
}
}
if (check) {
auto checkHeight = check->height();
trect.setHeight(trect.height() - checkHeight);
auto checkLeft = inner.left();
auto checkTop = trect.y() + trect.height();
if (point.y() >= checkTop && point.y() < checkTop + checkHeight) {
result = check->textState(
point - QPoint(checkLeft, checkTop),
request);
result.symbol += visibleTextLength()
+ visibleMediaTextLength();
}
}
auto checkBottomInfoState = [&] {
if (mediaOnBottom && (entry || media->customInfoLayout())) {
if (mediaOnBottom
&& (check || entry || media->customInfoLayout())) {
return;
}
const auto bottomInfoResult = bottomInfoTextState(
@ -2862,6 +2909,7 @@ void Message::updatePressed(QPoint point) {
TextForMimeData Message::selectedText(TextSelection selection) const {
const auto media = this->media();
auto logEntryOriginalResult = TextForMimeData();
auto factcheckResult = TextForMimeData();
const auto mediaDisplayed = (media && media->isDisplayed());
const auto mediaBefore = mediaDisplayed && invertMedia();
const auto textSelection = mediaBefore
@ -2876,7 +2924,15 @@ TextForMimeData Message::selectedText(TextSelection selection) const {
auto mediaResult = (mediaDisplayed || isHiddenByGroup())
? media->selectedText(mediaSelection)
: TextForMimeData();
if (auto entry = logEntryOriginal()) {
if (const auto check = factcheckBlock()) {
const auto checkSelection = mediaBefore
? skipTextSelection(textSelection)
: mediaDisplayed
? media->skipSelection(mediaSelection)
: skipTextSelection(selection);
factcheckResult = check->selectedText(checkSelection);
}
if (const auto entry = logEntryOriginal()) {
const auto originalSelection = mediaBefore
? skipTextSelection(textSelection)
: mediaDisplayed
@ -2892,6 +2948,11 @@ TextForMimeData Message::selectedText(TextSelection selection) const {
} else if (!second.empty()) {
result.append(u"\n\n"_q).append(std::move(second));
}
if (result.empty()) {
result = std::move(factcheckResult);
} else if (!factcheckResult.empty()) {
result.append(u"\n\n"_q).append(std::move(factcheckResult));
}
if (result.empty()) {
result = std::move(logEntryOriginalResult);
} else if (!logEntryOriginalResult.empty()) {
@ -2981,6 +3042,21 @@ TextSelection Message::adjustSelection(
? mediaAdjusted
: unskipTextSelection(mediaAdjusted);
}
auto checkResult = TextSelection();
if (const auto check = factcheckBlock()) {
auto checkSelection = !mediaDisplayed
? skipTextSelection(selection)
: mediaBefore
? skipTextSelection(textSelection)
: media->skipSelection(mediaSelection);
auto checkAdjusted = useSelection(checkSelection, true)
? check->adjustSelection(checkSelection, type)
: checkSelection;
checkResult = unskipTextSelection(checkAdjusted);
if (mediaDisplayed) {
checkResult = media->unskipSelection(checkResult);
}
}
auto entryResult = TextSelection();
if (const auto entry = logEntryOriginal()) {
auto entrySelection = !mediaDisplayed
@ -3003,6 +3079,12 @@ TextSelection Message::adjustSelection(
std::max(result.to, mediaResult.to),
};
}
if (!checkResult.empty()) {
result = result.empty() ? checkResult : TextSelection{
std::min(result.from, checkResult.from),
std::max(result.to, checkResult.to),
};
}
if (!entryResult.empty()) {
result = result.empty() ? entryResult : TextSelection{
std::min(result.from, entryResult.from),
@ -3410,6 +3492,13 @@ WebPage *Message::logEntryOriginal() const {
return nullptr;
}
WebPage *Message::factcheckBlock() const {
if (const auto entry = Get<Factcheck>()) {
return entry->page.get();
}
return nullptr;
}
bool Message::toggleSelectionByHandlerClick(
const ClickHandlerPtr &handler) const {
if (_comments && _comments->link == handler) {
@ -3539,7 +3628,9 @@ bool Message::drawBubble() const {
const auto item = data();
if (isHidden()) {
return false;
} else if (logEntryOriginal() || item->isFakeAboutView()) {
} else if (logEntryOriginal()
|| factcheckBlock()
|| item->isFakeAboutView()) {
return true;
}
const auto media = this->media();
@ -3560,7 +3651,7 @@ bool Message::unwrapped() const {
const auto item = data();
if (isHidden()) {
return true;
} else if (logEntryOriginal()) {
} else if (logEntryOriginal() || factcheckBlock()) {
return false;
}
const auto media = this->media();
@ -3921,8 +4012,22 @@ void Message::updateMediaInBubbleState() {
|| Has<Reply>()
|| item->Has<HistoryMessageVia>();
};
auto entry = logEntryOriginal();
if (entry) {
const auto entry = logEntryOriginal();
const auto check = factcheckBlock();
if (check) {
mediaHasSomethingBelow = true;
mediaHasSomethingAbove = getMediaHasSomethingAbove();
auto checkState = (mediaHasSomethingAbove
|| hasVisibleText()
|| (media && media->isDisplayed()))
? MediaInBubbleState::Bottom
: MediaInBubbleState::None;
check->setInBubbleState(checkState);
if (!media) {
check->setBubbleRounding(countBubbleRounding());
return;
}
} else if (entry) {
mediaHasSomethingBelow = true;
mediaHasSomethingAbove = getMediaHasSomethingAbove();
auto entryState = (mediaHasSomethingAbove
@ -3947,7 +4052,7 @@ void Message::updateMediaInBubbleState() {
return;
}
if (!entry) {
if (!check && !entry) {
mediaHasSomethingAbove = getMediaHasSomethingAbove();
}
if (!invertMedia() && hasVisibleText()) {
@ -4214,10 +4319,11 @@ int Message::resizeContentGetHeight(int newWidth) {
if (bubble) {
auto reply = Get<Reply>();
auto via = item->Get<HistoryMessageVia>();
auto entry = logEntryOriginal();
const auto check = factcheckBlock();
const auto entry = logEntryOriginal();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
if (reactionsInBubble) {
@ -4226,12 +4332,20 @@ int Message::resizeContentGetHeight(int newWidth) {
if (contentWidth == maxWidth()) {
if (mediaDisplayed) {
if (check) {
newHeight += check->resizeGetHeight(contentWidth);
}
if (entry) {
newHeight += entry->resizeGetHeight(contentWidth);
}
} else if (entry) {
// In case of text-only message it is counted in minHeight already.
entry->resizeGetHeight(contentWidth);
} else {
if (check) {
check->resizeGetHeight(contentWidth);
}
if (entry) {
// In case of text-only message it is counted in minHeight already.
entry->resizeGetHeight(contentWidth);
}
}
} else {
const auto withVisibleText = hasVisibleText();
@ -4251,15 +4365,24 @@ int Message::resizeContentGetHeight(int newWidth) {
if (!mediaOnTop) {
newHeight += st::msgPadding.top();
if (mediaDisplayed) newHeight += st::mediaInBubbleSkip;
if (check) newHeight += st::mediaInBubbleSkip;
if (entry) newHeight += st::mediaInBubbleSkip;
}
if (mediaDisplayed) {
newHeight += media->height();
if (check) {
newHeight += check->resizeGetHeight(contentWidth);
}
if (entry) {
newHeight += entry->resizeGetHeight(contentWidth);
}
} else {
if (check) {
newHeight += check->resizeGetHeight(contentWidth);
}
if (entry) {
newHeight += entry->resizeGetHeight(contentWidth);
}
} else if (entry) {
newHeight += entry->resizeGetHeight(contentWidth);
}
if (reactionsInBubble) {
if (!mediaDisplayed || _viewButton) {
@ -4343,9 +4466,12 @@ int Message::resizeContentGetHeight(int newWidth) {
bool Message::needInfoDisplay() const {
const auto media = this->media();
const auto mediaDisplayed = media ? media->isDisplayed() : false;
const auto check = factcheckBlock();
const auto entry = logEntryOriginal();
return entry
? !entry->customInfoLayout()
: check
? !check->customInfoLayout()
: ((mediaDisplayed && media->isBubbleBottom())
? !media->customInfoLayout()
: true);

View file

@ -47,6 +47,7 @@ struct LogEntryOriginal
struct Factcheck
: public RuntimeComponent<Factcheck, Element> {
std::unique_ptr<WebPage> page;
bool expanded = false;
};
struct PsaTooltipState : public RuntimeComponent<PsaTooltipState, Element> {
@ -294,6 +295,7 @@ private:
[[nodiscard]] int viewButtonHeight() const;
[[nodiscard]] WebPage *logEntryOriginal() const;
[[nodiscard]] WebPage *factcheckBlock() const;
[[nodiscard]] ClickHandlerPtr createGoToCommentsLink() const;
[[nodiscard]] ClickHandlerPtr psaTooltipLink() const;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_web_page.h"
#include "core/application.h"
#include "countries/countries_instance.h"
#include "base/qt/qt_key_modifiers.h"
#include "window/window_session_controller.h"
#include "iv/iv_instance.h"
@ -19,13 +20,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo_media.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_reply.h"
#include "history/view/history_view_sponsored_click_handler.h"
#include "history/view/media/history_view_media_common.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_message.h"
#include "history/view/history_view_reply.h"
#include "history/view/history_view_sponsored_click_handler.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "menu/menu_sponsored.h"
@ -36,13 +38,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/format_values.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
constexpr auto kMaxOriginalEntryLines = 8192;
constexpr auto kFactcheckCollapsedLines = 3;
constexpr auto kStickerSetLines = 3;
constexpr auto kFactcheckAboutDuration = 5 * crl::time(1000);
[[nodiscard]] int ArticleThumbWidth(not_null<PhotoData*> thumb, int height) {
const auto size = thumb->location(Data::PhotoSize::Thumbnail);
@ -148,6 +153,39 @@ constexpr auto kStickerSetLines = 3;
});
}
[[nodiscard]] ClickHandlerPtr AboutFactcheckClickHandler(QString iso2) {
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
const auto show = my.show
? my.show
: controller
? controller->uiShow()
: nullptr;
if (show) {
const auto name = Countries::Instance().countryNameByISO2(iso2);
const auto use = name.isEmpty() ? iso2 : name;
show->showToast({
.text = { tr::lng_factcheck_about(tr::now, lt_country, use) },
.duration = kFactcheckAboutDuration,
});
}
});
}
[[nodiscard]] ClickHandlerPtr ToggleFactcheckClickHandler(
not_null<Element*> view) {
const auto weak = base::make_weak(view);
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
if (const auto strong = weak.get()) {
if (const auto factcheck = strong->Get<Factcheck>()) {
factcheck->expanded = !factcheck->expanded;
strong->history()->owner().requestViewResize(strong);
}
}
});
}
[[nodiscard]] TextWithEntities PageToPhrase(not_null<WebPageData*> page) {
const auto type = page->type;
const auto text = Ui::Text::Upper(page->iv
@ -234,32 +272,55 @@ WebPage::WebPage(
: Media(parent)
, _st(st::historyPagePreview)
, _data(data)
, _sponsoredData([&]() -> std::optional<SponsoredData> {
if (!(flags & MediaWebPageFlag::Sponsored)) {
return std::nullopt;
}
const auto &session = _parent->data()->history()->session();
const auto details = session.sponsoredMessages().lookupDetails(
_parent->data()->fullId());
auto result = std::make_optional<SponsoredData>();
result->buttonText = details.buttonText;
result->isLinkInternal = details.isLinkInternal;
result->backgroundEmojiId = details.backgroundEmojiId;
result->colorIndex = details.colorIndex;
result->canReport = details.canReport;
return result;
}())
, _flags(flags)
, _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())
, _flags(flags) {
, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) {
history()->owner().registerWebPageView(_data, _parent);
}
void WebPage::setupAdditionalData() {
if (_flags & MediaWebPageFlag::Sponsored) {
_additionalData = std::make_unique<AdditionalData>(SponsoredData());
const auto raw = sponsoredData();
const auto &session = _parent->data()->history()->session();
const auto details = session.sponsoredMessages().lookupDetails(
_parent->data()->fullId());
raw->buttonText = details.buttonText;
raw->isLinkInternal = details.isLinkInternal ? 1 : 0;
raw->backgroundEmojiId = details.backgroundEmojiId;
raw->colorIndex = details.colorIndex;
raw->canReport = details.canReport ? 1 : 0;
} else if (_data->stickerSet) {
_additionalData = std::make_unique<AdditionalData>(StickerSetData());
const auto raw = stickerSetData();
for (const auto &sticker : _data->stickerSet->items) {
if (!sticker->sticker()) {
continue;
}
raw->views.push_back(
std::make_unique<Sticker>(_parent, sticker, true));
}
const auto side = std::ceil(std::sqrt(raw->views.size()));
const auto box = UnitedLineHeight() * kStickerSetLines;
const auto single = box / side;
for (const auto &view : raw->views) {
view->setWebpagePart();
view->initSize(single);
}
} else if (_data->type == WebPageType::Factcheck) {
_additionalData = std::make_unique<AdditionalData>(FactcheckData());
}
}
QSize WebPage::countOptimalSize() {
if (_data->pendingTill || _data->failed) {
return { 0, 0 };
}
setupAdditionalData();
const auto sponsored = sponsoredData();
const auto factcheck = factcheckData();
// Detect _openButtonWidth before counting paddings.
_openButton = Ui::Text::String();
@ -274,12 +335,10 @@ QSize WebPage::countOptimalSize() {
PageToPhrase(_data),
kMarkupTextOptions,
context);
} else if (_sponsoredData) {
if (!_sponsoredData->buttonText.isEmpty()) {
_openButton.setText(
st::semiboldTextStyle,
Ui::Text::Upper(_sponsoredData->buttonText));
}
} else if (sponsored && !sponsored->buttonText.isEmpty()) {
_openButton.setText(
st::semiboldTextStyle,
Ui::Text::Upper(sponsored->buttonText));
}
const auto padding = inBubblePadding() + innerMargin();
@ -296,25 +355,7 @@ QSize WebPage::countOptimalSize() {
}
const auto lineHeight = UnitedLineHeight();
if (_data->stickerSet && !_stickerSet) {
_stickerSet = std::make_unique<StickerSet>();
for (const auto &sticker : _data->stickerSet->items) {
if (!sticker->sticker()) {
continue;
}
_stickerSet->views.push_back(
std::make_unique<Sticker>(_parent, sticker, true));
}
const auto side = std::ceil(std::sqrt(_stickerSet->views.size()));
const auto box = lineHeight * kStickerSetLines;
const auto single = box / side;
for (const auto &view : _stickerSet->views) {
view->setWebpagePart();
view->initSize(single);
}
}
if (!_openl && (!_data->url.isEmpty() || _sponsoredData)) {
if (!_openl && (!_data->url.isEmpty() || sponsored || factcheck)) {
const auto original = _parent->data()->originalText();
const auto previewOfHiddenUrl = [&] {
if (_data->type == WebPageType::BotApp) {
@ -352,28 +393,31 @@ QSize WebPage::countOptimalSize() {
}
return true;
}();
_openl = _data->iv
? IvClickHandler(_data, original)
: (previewOfHiddenUrl || UrlClickHandler::IsSuspicious(
_data->url))
? std::make_shared<HiddenUrlClickHandler>(_data->url)
: std::make_shared<UrlClickHandler>(_data->url, true);
if (_data->document
if (sponsored) {
_openl = SponsoredLink(_data->url, sponsored->isLinkInternal);
if (sponsored->canReport) {
sponsored->hint.link = AboutSponsoredClickHandler();
}
} else if (factcheck) {
const auto item = _parent->data();
if (const auto info = item->Get<HistoryMessageFactcheck>()) {
const auto country = info->data.country;
factcheck->hint.link = AboutFactcheckClickHandler(country);
}
} else if (_data->document
&& (_data->document->isWallPaper()
|| _data->document->isTheme())) {
_openl = std::make_shared<DocumentWrappedClickHandler>(
std::move(_openl),
_data->document,
_parent->data()->fullId());
}
if (_sponsoredData) {
_openl = SponsoredLink(
_data->url,
_sponsoredData->isLinkInternal);
if (_sponsoredData->canReport) {
_sponsoredData->hintLink = AboutSponsoredClickHandler();
}
} else {
_openl = _data->iv
? IvClickHandler(_data, original)
: (previewOfHiddenUrl || UrlClickHandler::IsSuspicious(
_data->url))
? std::make_shared<HiddenUrlClickHandler>(_data->url)
: std::make_shared<UrlClickHandler>(_data->url, true);
}
}
@ -456,7 +500,12 @@ QSize WebPage::countOptimalSize() {
const auto siteNameHeight = _siteName.isEmpty() ? 0 : lineHeight;
const auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
const auto descMaxLines = isLogEntryOriginal()
const auto factcheckMetrics = factcheck
? computeFactcheckMetrics(_description.minHeight())
: FactcheckMetrics();
const auto descMaxLines = factcheck
? factcheckMetrics.lines
: isLogEntryOriginal()
? kMaxOriginalEntryLines
: (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1));
const auto descriptionMinHeight = _description.isEmpty()
@ -517,15 +566,16 @@ QSize WebPage::countOptimalSize() {
if (_asArticle) {
minHeight = resizeGetHeight(maxWidth);
}
if (_sponsoredData && _sponsoredData->canReport) {
_sponsoredData->widthBeforeHint
= st::webPageTitleStyle.font->width(siteName);
if (const auto hint = hintData()) {
hint->widthBefore = st::webPageTitleStyle.font->width(siteName);
const auto &font = st::webPageSponsoredHintFont;
_sponsoredData->hintSize = QSize(
font->width(tr::lng_sponsored_message_revenue_button(tr::now))
+ font->height,
hint->text = sponsored
? tr::lng_sponsored_message_revenue_button(tr::now)
: tr::lng_factcheck_whats_this(tr::now);
hint->size = QSize(
font->width(hint->text) + font->height,
font->height);
maxWidth += _sponsoredData->hintSize.width();
maxWidth += hint->size.width();
}
return { maxWidth, minHeight };
}
@ -539,9 +589,22 @@ QSize WebPage::countCurrentSize(int newWidth) {
const auto innerWidth = newWidth - rect::m::sum::h(padding);
auto newHeight = 0;
const auto specialRightPix = (_sponsoredData || _stickerSet);
const auto stickerSet = stickerSetData();
const auto factcheck = factcheckData();
const auto specialRightPix = (sponsoredData() || stickerSet);
const auto lineHeight = UnitedLineHeight();
const auto linesMax = (specialRightPix || isLogEntryOriginal())
const auto factcheckMetrics = factcheck
? computeFactcheckMetrics(_description.countHeight(innerWidth))
: FactcheckMetrics();
if (factcheck) {
factcheck->expandable = factcheckMetrics.expandable;
_openl = factcheck->expandable
? ToggleFactcheckClickHandler(_parent)
: nullptr;
}
const auto linesMax = factcheck
? (factcheckMetrics.lines + 1)
: (specialRightPix || isLogEntryOriginal())
? kMaxOriginalEntryLines
: 5;
const auto siteNameHeight = _siteNameLines ? lineHeight : 0;
@ -550,7 +613,7 @@ QSize WebPage::countCurrentSize(int newWidth) {
if (asArticle() || specialRightPix) {
constexpr auto kSponsoredUserpicLines = 2;
_pixh = lineHeight
* (_stickerSet
* (stickerSet
? kStickerSetLines
: specialRightPix
? kSponsoredUserpicLines
@ -673,8 +736,14 @@ void WebPage::ensurePhotoMediaCreated() const {
}
bool WebPage::hasHeavyPart() const {
if (const auto stickerSet = stickerSetData()) {
for (const auto &part : stickerSet->views) {
if (part->hasHeavyPart()) {
return true;
}
}
}
return _photoMedia
|| (_stickerSet)
|| (_attach ? _attach->hasHeavyPart() : false);
}
@ -684,6 +753,11 @@ void WebPage::unloadHeavyPart() {
}
_description.unloadPersistentAnimation();
_photoMedia = nullptr;
if (const auto stickerSet = stickerSetData()) {
for (const auto &part : stickerSet->views) {
part->unloadHeavyPart();
}
}
}
void WebPage::draw(Painter &p, const PaintContext &context) const {
@ -704,22 +778,22 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
auto tshift = inner.top();
auto paintw = inner.width();
const auto asSponsored = (!!_sponsoredData);
const auto sponsored = sponsoredData();
const auto selected = context.selected();
const auto view = parent();
const auto from = view->data()->contentColorsFrom();
const auto colorIndex = (asSponsored && _sponsoredData->colorIndex)
? _sponsoredData->colorIndex
const auto colorIndex = (sponsored && sponsored->colorIndex)
? sponsored->colorIndex
: from
? from->colorIndex()
: view->colorIndex();
const auto cache = context.outbg
? stm->replyCache[st->colorPatternIndex(colorIndex)].get()
: st->coloredReplyCache(selected, colorIndex).get();
const auto backgroundEmojiId = (asSponsored
&& _sponsoredData->backgroundEmojiId)
? _sponsoredData->backgroundEmojiId
const auto backgroundEmojiId = (sponsored
&& sponsored->backgroundEmojiId)
? sponsored->backgroundEmojiId
: from
? from->backgroundEmojiId()
: DocumentId();
@ -755,8 +829,8 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
}
auto lineHeight = UnitedLineHeight();
if (_stickerSet) {
const auto viewsCount = _stickerSet->views.size();
if (const auto stickerSet = stickerSetData()) {
const auto viewsCount = stickerSet->views.size();
const auto box = _pixh;
const auto topLeft = QPoint(inner.left() + paintw - box, tshift);
const auto side = std::ceil(std::sqrt(viewsCount));
@ -767,7 +841,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
if (viewsCount <= index) {
break;
}
const auto &view = _stickerSet->views[index];
const auto &view = stickerSet->views[index];
const auto size = view->countOptimalSize();
const auto offsetX = (single - size.width()) / 2.;
const auto offsetY = (single - size.height()) / 2.;
@ -822,7 +896,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
st->msgSelectOverlay(),
st->msgSelectOverlayCorners(Ui::CachedCornerRadius::Small));
}
if (!asSponsored) {
if (!sponsored) {
// Ignore photo width in sponsored messages,
// as its width only affects the title.
paintw -= pw + st::webPagePhotoDelta;
@ -850,35 +924,31 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
endskip,
false,
context.selection);
if (asSponsored
&& _sponsoredData->canReport
&& (paintw >
_sponsoredData->widthBeforeHint
+ _sponsoredData->hintSize.width())) {
if (_sponsoredData->hintRipple) {
_sponsoredData->hintRipple->paint(
p,
_sponsoredData->lastHintPos.x(),
_sponsoredData->lastHintPos.y(),
width(),
&cache->bg);
if (_sponsoredData->hintRipple->empty()) {
_sponsoredData->hintRipple = nullptr;
}
}
const auto hint = hintData();
if (hint && (paintw > hint->widthBefore + hint->size.width())) {
auto color = cache->icon;
color.setAlphaF(color.alphaF() * 0.15);
const auto height = st::webPageSponsoredHintFont->height;
const auto radius = height / 2;
_sponsoredData->lastHintPos = QPointF(
radius + inner.left() + _sponsoredData->widthBeforeHint,
hint->lastPosition = QPointF(
radius + inner.left() + hint->widthBefore,
tshift + (_siteName.style()->font->height - height) / 2.);
const auto rect = QRectF(
_sponsoredData->lastHintPos,
_sponsoredData->hintSize);
if (hint->ripple) {
hint->ripple->paint(
p,
hint->lastPosition.x(),
hint->lastPosition.y(),
width(),
&cache->bg);
if (hint->ripple->empty()) {
hint->ripple = nullptr;
}
}
const auto rect = QRectF(hint->lastPosition, hint->size);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(color);
@ -887,10 +957,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
p.setPen(cache->icon);
p.setBrush(Qt::NoBrush);
p.setFont(st::webPageSponsoredHintFont);
p.drawText(
rect,
tr::lng_sponsored_message_revenue_button(tr::now),
style::al_center);
p.drawText(rect, hint->text, style::al_center);
}
tshift += lineHeight;
@ -901,7 +968,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
const auto endskip = _title.hasSkipBlock()
? _parent->skipBlockWidth()
: 0;
const auto titleWidth = asSponsored
const auto titleWidth = sponsored
? (paintw - _pixh - st::webPagePhotoDelta)
: paintw;
_title.drawLeftElided(
@ -1051,16 +1118,38 @@ bool WebPage::asArticle() const {
return _asArticle && (_data->photo != nullptr);
}
WebPage::StickerSetData *WebPage::stickerSetData() const {
return std::get_if<StickerSetData>(_additionalData.get());
}
WebPage::SponsoredData *WebPage::sponsoredData() const {
return std::get_if<SponsoredData>(_additionalData.get());
}
WebPage::FactcheckData *WebPage::factcheckData() const {
return std::get_if<FactcheckData>(_additionalData.get());
}
WebPage::HintData *WebPage::hintData() const {
if (const auto sponsored = sponsoredData()) {
return sponsored->hint.link ? &sponsored->hint : nullptr;
} else if (const auto factcheck = factcheckData()) {
return factcheck->hint.link ? &factcheck->hint : nullptr;
}
return nullptr;
}
TextState WebPage::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (width() < rect::m::sum::h(st::msgPadding) + 1) {
return result;
}
const auto sponsored = sponsoredData();
const auto bubble = _attach ? _attach->bubbleMargins() : QMargins();
const auto full = Rect(currentSize());
auto outer = full - inBubblePadding();
if (_sponsoredData) {
if (sponsored) {
outer.translate(0, st::msgDateFont->height);
}
const auto inner = outer - innerMargin();
@ -1175,16 +1264,15 @@ TextState WebPage::textState(QPoint point, StateRequest request) const {
}
}
}
if ((!result.link || _sponsoredData) && outer.contains(point)) {
if ((!result.link || sponsored) && outer.contains(point)) {
result.link = _openl;
}
if (_sponsoredData && _sponsoredData->canReport) {
const auto contains = QRectF(
_sponsoredData->lastHintPos,
_sponsoredData->hintSize).contains(point
- QPoint(0, st::msgDateFont->height));
if (contains) {
result.link = _sponsoredData->hintLink;
if (const auto hint = hintData()) {
const auto check = point
- QPoint(0, sponsored ? st::msgDateFont->height : 0);
const auto hintRect = QRectF(hint->lastPosition, hint->size);
if (hintRect.contains(check)) {
result.link = hint->link;
}
}
_lastPoint = point - outer.topLeft();
@ -1256,25 +1344,25 @@ void WebPage::clickHandlerActiveChanged(
void WebPage::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
if (_sponsoredData && _sponsoredData->hintLink == p) {
const auto hint = hintData();
if (hint && hint->link == p) {
if (pressed) {
if (!_sponsoredData->hintRipple) {
if (!hint->ripple) {
const auto owner = &parent()->history()->owner();
auto ripple = std::make_unique<Ui::RippleAnimation>(
hint->ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
Ui::RippleAnimation::RoundRectMask(
_sponsoredData->hintSize,
hint->size,
_st.radius),
[=] { owner->requestViewRepaint(parent()); });
_sponsoredData->hintRipple = std::move(ripple);
}
const auto full = Rect(currentSize());
const auto outer = full - inBubblePadding();
_sponsoredData->hintRipple->add(_lastPoint
hint->ripple->add(_lastPoint
+ outer.topLeft()
- _sponsoredData->lastHintPos.toPoint());
} else if (_sponsoredData->hintRipple) {
_sponsoredData->hintRipple->lastStop();
- hint->lastPosition.toPoint());
} else if (hint->ripple) {
hint->ripple->lastStop();
}
return;
}
@ -1387,6 +1475,19 @@ bool WebPage::isLogEntryOriginal() const {
return _parent->data()->isAdminLogEntry() && _parent->media() != this;
}
WebPage::FactcheckMetrics WebPage::computeFactcheckMetrics(
int fullHeight) const {
const auto possible = fullHeight / st::normalFont->height;
const auto expandable = (possible > kFactcheckCollapsedLines + 1);
const auto check = _parent->Get<Factcheck>();
const auto expanded = check && check->expanded;
const auto allowExpanding = (expanded || !expandable);
return {
.lines = allowExpanding ? possible : kFactcheckCollapsedLines,
.expandable = expandable,
};
}
int WebPage::bottomInfoPadding() const {
if (!isBubbleBottom()) {
return 0;

View file

@ -101,6 +101,40 @@ public:
~WebPage();
private:
struct FactcheckMetrics {
int lines = 0;
bool expandable = false;
};
struct HintData {
QSize size;
QPointF lastPosition;
QString text;
int widthBefore = 0;
std::unique_ptr<Ui::RippleAnimation> ripple;
ClickHandlerPtr link;
};
struct StickerSetData {
std::vector<std::unique_ptr<Sticker>> views;
};
struct SponsoredData {
QString buttonText;
uint64 backgroundEmojiId = 0;
uint8 colorIndex : 6 = 0;
uint8 isLinkInternal : 1 = 0;
uint8 canReport : 1 = 0;
HintData hint;
};
struct FactcheckData {
HintData hint;
bool expandable = false;
};
using AdditionalData = std::variant<
StickerSetData,
SponsoredData,
FactcheckData>;
void playAnimation(bool autoplay) override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
@ -124,36 +158,26 @@ private:
const ClickHandlerPtr &link) const;
[[nodiscard]] bool asArticle() const;
[[nodiscard]] StickerSetData *stickerSetData() const;
[[nodiscard]] SponsoredData *sponsoredData() const;
[[nodiscard]] FactcheckData *factcheckData() const;
[[nodiscard]] HintData *hintData() const;
[[nodiscard]] FactcheckMetrics computeFactcheckMetrics(
int fullHeight) const;
void setupAdditionalData();
const style::QuoteStyle &_st;
const not_null<WebPageData*> _data;
const MediaWebPageFlags _flags;
std::vector<std::unique_ptr<Data::Media>> _collage;
ClickHandlerPtr _openl;
std::unique_ptr<Media> _attach;
mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
mutable std::unique_ptr<Ui::RippleAnimation> _ripple;
struct StickerSet final {
std::vector<std::unique_ptr<Sticker>> views;
};
std::unique_ptr<StickerSet> _stickerSet;
struct SponsoredData final {
QString buttonText;
bool isLinkInternal = false;
uint64 backgroundEmojiId = 0;
uint8 colorIndex : 6 = 0;
bool canReport = false;
QSize hintSize;
QPointF lastHintPos;
int widthBeforeHint = 0;
std::unique_ptr<Ui::RippleAnimation> hintRipple;
ClickHandlerPtr hintLink;
};
mutable std::optional<SponsoredData> _sponsoredData;
int _dataVersion = -1;
int _siteNameLines = 0;
int _descriptionLines = 0;
@ -172,7 +196,7 @@ private:
int _pixw = 0;
int _pixh = 0;
const MediaWebPageFlags _flags;
std::unique_ptr<AdditionalData> _additionalData;
};