Support sponsored peers in search results.

This commit is contained in:
John Preston 2025-03-20 17:06:02 +04:00
parent 33c5b35444
commit a0764190f2
17 changed files with 546 additions and 227 deletions

View file

@ -4392,6 +4392,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_search_filter_private" = "Private chats";
"lng_search_filter_group" = "Group chats";
"lng_search_filter_channel" = "Channels";
"lng_search_sponsored_button" = "Ad ⋮";
"lng_media_save_progress" = "{ready} of {total} {mb}";
"lng_mediaview_save_as" = "Save As...";
@ -5806,6 +5807,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sponsored_revenued_info1_title" = "Respect Your Privacy";
"lng_sponsored_revenued_info1_description" = "Ads on Telegram do not use your personal information and are based on the channel in which you see them.";
"lng_sponsored_revenued_info1_bot_description" = "Ads on Telegram do not use your personal information and are based on the mini app in which you see them.";
"lng_sponsored_revenued_info1_search_description" = "Ads on Telegram do not use your personal information and are based on the search query you entered.";
"lng_sponsored_revenued_info2_title" = "Help the Channel Creator";
"lng_sponsored_revenued_info2_bot_title" = "Help the Bot Developer";
"lng_sponsored_revenued_info2_description" = "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed.";
@ -5814,9 +5816,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sponsored_revenued_info3_description#one" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
"lng_sponsored_revenued_info3_description#other" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
"lng_sponsored_revenued_info3_bot_description" = "You can turn off ads in mini apps by subscribing to {link}.";
"lng_sponsored_revenued_info3_search_description" = "You can turn off ads by subscribing to Telegram Premium. {link}";
"lng_sponsored_revenued_info3_search_link" = "Subscribe {arrow}";
"lng_sponsored_revenued_footer_title" = "Can I Launch an Ad?";
"lng_sponsored_revenued_footer_description" = "Anyone can create an ad to display in this channel — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
"lng_sponsored_revenued_footer_bot_description" = "Anyone can create an ad to display in this bot — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
"lng_sponsored_revenued_footer_search_description" = "Anyone can create an ad to display in search results for any query. Check out the **Telegram Ad Platform** for details. {link}";
"lng_sponsored_top_bar_hide" = "remove";
"lng_telegram_features_url" = "https://t.me/TelegramTips";

View file

@ -106,9 +106,10 @@ void PeerSearch::requestSponsored() {
parsed.sponsored.push_back({
.peer = _session->data().peer(peerId),
.randomId = data.vrandom_id().v,
.sponsorInfo = qs(data.vsponsor_info().value_or_empty()),
.additionalInfo = qs(
data.vadditional_info().value_or_empty()),
.sponsorInfo = TextWithEntities::Simple(
qs(data.vsponsor_info().value_or_empty())),
.additionalInfo = TextWithEntities::Simple(
qs(data.vadditional_info().value_or_empty())),
});
}
finishSponsored(requestId, std::move(parsed));

View file

@ -16,8 +16,8 @@ namespace Api {
struct SponsoredSearchResult {
not_null<PeerData*> peer;
QByteArray randomId;
QString sponsorInfo;
QString additionalInfo;
TextWithEntities sponsorInfo;
TextWithEntities additionalInfo;
};
struct PeerSearchResult {

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/components/sponsored_messages.h"
#include "api/api_text_entities.h"
#include "api/api_peer_search.h" // SponsoredSearchResult
#include "apiwrap.h"
#include "core/click_handler_types.h"
#include "data/data_channel.h"
@ -33,6 +34,19 @@ constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000);
return (received > 0) && (received + kRequestTimeLimit > crl::now());
}
template <typename Fields>
[[nodiscard]] std::vector<TextWithEntities> Prepare(const Fields &fields) {
using InfoList = std::vector<TextWithEntities>;
return (!fields.sponsorInfo.text.isEmpty()
&& !fields.additionalInfo.text.isEmpty())
? InfoList{ fields.sponsorInfo, fields.additionalInfo }
: !fields.sponsorInfo.text.isEmpty()
? InfoList{ fields.sponsorInfo }
: !fields.additionalInfo.text.isEmpty()
? InfoList{ fields.additionalInfo }
: InfoList{};
}
} // namespace
SponsoredMessages::SponsoredMessages(not_null<Main::Session*> session)
@ -535,18 +549,8 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails(
return {};
}
const auto &data = entryPtr->sponsored;
using InfoList = std::vector<TextWithEntities>;
auto info = (!data.sponsorInfo.text.isEmpty()
&& !data.additionalInfo.text.isEmpty())
? InfoList{ data.sponsorInfo, data.additionalInfo }
: !data.sponsorInfo.text.isEmpty()
? InfoList{ data.sponsorInfo }
: !data.additionalInfo.text.isEmpty()
? InfoList{ data.additionalInfo }
: InfoList{};
return {
.info = std::move(info),
.info = Prepare(data),
.link = data.link,
.buttonText = data.from.buttonText,
.photoId = data.from.photoId,
@ -559,6 +563,14 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails(
};
}
SponsoredMessages::Details SponsoredMessages::lookupDetails(
const Api::SponsoredSearchResult &data) const {
return {
.info = Prepare(data),
.canReport = true,
};
}
void SponsoredMessages::clicked(
const FullMsgId &fullId,
bool isMedia,
@ -583,9 +595,29 @@ void SponsoredMessages::clicked(
)).send();
}
SponsoredReportAction SponsoredMessages::createReportCallback(
const FullMsgId &fullId) {
const auto entry = find(fullId);
if (!entry) {
return { .callback = [=](const auto &...) {} };
}
const auto history = _session->data().history(fullId.peer);
const auto erase = [=] {
const auto it = _data.find(history);
if (it != end(_data)) {
auto &list = it->second.entries;
const auto proj = [&](const Entry &e) {
return e.itemFullId == fullId;
};
list.erase(ranges::remove_if(list, proj), end(list));
}
};
return createReportCallback(entry->sponsored.randomId, erase);
}
auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
-> Fn<void(SponsoredReportResult::Id, Fn<void(SponsoredReportResult)>)> {
SponsoredReportAction SponsoredMessages::createReportCallback(
const QByteArray &randomId,
Fn<void()> erase) {
using TLChoose = MTPDchannels_sponsoredMessageReportResultChooseOption;
using TLAdsHidden = MTPDchannels_sponsoredMessageReportResultAdsHidden;
using TLReported = MTPDchannels_sponsoredMessageReportResultReported;
@ -601,25 +633,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
};
const auto state = std::make_shared<State>();
return [=](Result::Id optionId, Fn<void(Result)> done) {
const auto entry = find(fullId);
if (!entry) {
return;
}
const auto history = _session->data().history(fullId.peer);
const auto erase = [=] {
const auto it = _data.find(history);
if (it != end(_data)) {
auto &list = it->second.entries;
const auto proj = [&](const Entry &e) {
return e.itemFullId == fullId;
};
list.erase(ranges::remove_if(list, proj), end(list));
}
};
return { .callback = [=](Result::Id optionId, Fn<void(Result)> done) {
if (optionId == Result::Id("-1")) {
erase();
return;
@ -627,7 +641,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
state->requestId = _session->api().request(
MTPmessages_ReportSponsoredMessage(
MTP_bytes(entry->sponsored.randomId),
MTP_bytes(randomId),
MTP_bytes(optionId))
).done([=](
const MTPchannels_SponsoredMessageReportResult &result,
@ -664,7 +678,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
done({ .error = error.type() });
}
}).send();
};
} };
}
SponsoredMessages::State SponsoredMessages::state(

View file

@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class History;
namespace Api {
struct SponsoredSearchResult;
} // namespace Api
namespace Main {
class Session;
} // namespace Main
@ -69,6 +73,25 @@ struct SponsoredMessage {
TextWithEntities additionalInfo;
};
struct SponsoredMessageDetails {
std::vector<TextWithEntities> info;
QString link;
QString buttonText;
PhotoId photoId = PhotoId(0);
PhotoId mediaPhotoId = PhotoId(0);
DocumentId mediaDocumentId = DocumentId(0);
uint64 backgroundEmojiId = 0;
uint8 colorIndex : 6 = 0;
bool isLinkInternal = false;
bool canReport = false;
};
struct SponsoredReportAction {
Fn<void(
Data::SponsoredReportResult::Id,
Fn<void(Data::SponsoredReportResult)>)> callback;
};
class SponsoredMessages final {
public:
enum class AppendResult {
@ -82,18 +105,7 @@ public:
InjectToMiddle,
AppendToTopBar,
};
struct Details {
std::vector<TextWithEntities> info;
QString link;
QString buttonText;
PhotoId photoId = PhotoId(0);
PhotoId mediaPhotoId = PhotoId(0);
DocumentId mediaDocumentId = DocumentId(0);
uint64 backgroundEmojiId = 0;
uint8 colorIndex : 6 = 0;
bool isLinkInternal = false;
bool canReport = false;
};
using Details = SponsoredMessageDetails;
using RandomId = QByteArray;
explicit SponsoredMessages(not_null<Main::Session*> session);
~SponsoredMessages();
@ -103,6 +115,8 @@ public:
void request(not_null<History*> history, Fn<void()> done);
void clearItems(not_null<History*> history);
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
[[nodiscard]] Details lookupDetails(
const Api::SponsoredSearchResult &data) const;
void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen);
void clicked(
const QByteArray &randomId,
@ -125,8 +139,11 @@ public:
[[nodiscard]] State state(not_null<History*> history) const;
[[nodiscard]] auto createReportCallback(const FullMsgId &fullId)
-> Fn<void(SponsoredReportResult::Id, Fn<void(SponsoredReportResult)>)>;
[[nodiscard]] SponsoredReportAction createReportCallback(
const FullMsgId &fullId);
[[nodiscard]] SponsoredReportAction createReportCallback(
const QByteArray &randomId,
Fn<void()> erase);
void clear();

View file

@ -24,6 +24,10 @@ DialogRow {
unreadMarkDiameter: pixels;
tagTop: pixels;
}
DialogRightButton {
button: RoundButton;
margin: margins;
}
ThreeStateIcon {
icon: icon;
@ -115,11 +119,16 @@ dialogRowFilterTagSkip: 4px;
dialogRowFilterTagStyle: TextStyle(defaultTextStyle) {
font: font(10px);
}
dialogRowOpenBotTextStyle: semiboldTextStyle;
dialogRowOpenBotHeight: 20px;
dialogRowOpenBotRight: 10px;
dialogRowOpenBotTop: 32px;
dialogRowOpenBotRecentTop: 28px;
dialogRowOpenBot: DialogRightButton {
button: RoundButton(defaultActiveButton) {
height: 20px;
textTop: 1px;
}
margin: margins(0px, 32px, 10px, 0px);
}
dialogRowOpenBotRecent: DialogRightButton(dialogRowOpenBot) {
margin: margins(0px, 32px, 28px, 0px);
}
forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }};
forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }};
@ -792,3 +801,15 @@ dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) {
dialogsQuickActionSize: 20px;
dialogsQuickActionRippleSize: 80px;
dialogsSponsoredButton: DialogRightButton(dialogRowOpenBot) {
button: RoundButton(defaultLightButton) {
textFg: windowActiveTextFg;
textFgOver: windowActiveTextFg;
textBg: lightButtonBgOver;
textBgOver: lightButtonBgOver;
height: 20px;
textTop: 1px;
}
margin: margins(0px, 9px, 10px, 0px);
}

View file

@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace style {
struct DialogRightButton;
} // namespace style
namespace Ui {
class RippleAnimation;
} // namespace Ui
@ -114,11 +118,16 @@ struct RowsByLetter {
};
struct RightButton final {
const style::DialogRightButton *st = nullptr;
QImage bg;
QImage selectedBg;
QImage activeBg;
Ui::Text::String text;
std::unique_ptr<Ui::RippleAnimation> ripple;
explicit operator bool() const {
return st != nullptr;
}
};
} // namespace Dialogs

View file

@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "menu/menu_sponsored.h"
#include "window/notifications_manager.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
@ -241,12 +242,17 @@ struct InnerWidget::HashtagResult {
BasicRow row;
};
struct InnerWidget::SponsoredSearchResult {
Api::SponsoredSearchResult data;
RightButton button;
};
struct InnerWidget::PeerSearchResult {
explicit PeerSearchResult(not_null<PeerData*> peer) : peer(peer) {
}
not_null<PeerData*> peer;
std::unique_ptr<Api::SponsoredSearchResult> sponsored;
std::unique_ptr<SponsoredSearchResult> sponsored;
mutable Ui::Text::String name;
mutable Ui::PeerBadge badge;
BasicRow row;
@ -287,6 +293,12 @@ InnerWidget::InnerWidget(
_topicJumpCache = nullptr;
_chatsFilterTags.clear();
_rightButtons.clear();
_pressedRightButtonData = nullptr;
for (const auto &result : _peerSearchResults) {
if (const auto sponsored = result->sponsored.get()) {
sponsored->button = {};
}
}
}, lifetime());
session().downloaderTaskFinished(
@ -1139,17 +1151,32 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
&& r.y() <= (skip + from * st::dialogsRowHeight)
&& r.y() + r.height() >= (skip + (from + 1) * st::dialogsRowHeight)) {
session().sponsoredMessages().view(
result->sponsored->randomId);
result->sponsored->data.randomId);
}
const auto peer = result->peer;
const auto active = !activeEntry.fullId
&& activePeer
&& ((peer == activePeer)
|| (peer->migrateTo() == activePeer));
const auto selected = (from == (isPressed()
const auto selected = (from == ((_peerSearchMenu >= 0)
? _peerSearchMenu
: isPressed()
? _peerSearchPressed
: _peerSearchSelected));
if (result->sponsored
&& result->sponsored->button.text.isEmpty()) {
fillRightButton(
result->sponsored->button,
tr::lng_search_sponsored_button(
tr::now,
Ui::Text::WithEntities),
st::dialogsSponsoredButton);
}
paintPeerSearchResult(p, result.get(), {
.rightButton = (result->sponsored
? &result->sponsored->button
: nullptr),
.st = &st::defaultDialogRow,
.currentBg = currentBg(),
.now = ms,
@ -1307,36 +1334,47 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
}
}
void InnerWidget::fillRightButton(
RightButton &button,
const TextWithEntities &text,
const style::DialogRightButton &st) {
button.st = &st;
button.text.setMarkedText(st.button.style, text);
const auto size = QSize(
button.text.maxWidth() + button.text.minHeight(),
st.button.height);
const auto generateBg = [&](const style::color &c) {
auto bg = QImage(
style::DevicePixelRatio() * size,
QImage::Format_ARGB32_Premultiplied);
bg.setDevicePixelRatio(style::DevicePixelRatio());
bg.fill(Qt::transparent);
{
auto p = QPainter(&bg);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(c);
const auto r = size.height() / 2;
p.drawRoundedRect(Rect(size), r, r);
}
return bg;
};
button.bg = generateBg(st.button.textBg);
button.selectedBg = generateBg(st.button.textBgOver);
button.activeBg = generateBg(st.button.textFg);
}
[[nodiscard]] RightButton *InnerWidget::maybeCacheRightButton(Row *row) {
if (const auto user = MaybeBotWithApp(row)) {
const auto it = _rightButtons.find(user->id);
if (it == _rightButtons.end()) {
auto rightButton = RightButton();
const auto text = tr::lng_profile_open_app_short(tr::now);
rightButton.text.setText(st::dialogRowOpenBotTextStyle, text);
const auto size = QSize(
rightButton.text.maxWidth()
+ rightButton.text.minHeight(),
st::dialogRowOpenBotHeight);
const auto generateBg = [&](const style::color &c) {
auto bg = QImage(
style::DevicePixelRatio() * size,
QImage::Format_ARGB32_Premultiplied);
bg.setDevicePixelRatio(style::DevicePixelRatio());
bg.fill(Qt::transparent);
{
auto p = QPainter(&bg);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(c);
const auto r = size.height() / 2;
p.drawRoundedRect(Rect(size), r, r);
}
return bg;
};
rightButton.bg = generateBg(st::activeButtonBg);
rightButton.selectedBg = generateBg(st::activeButtonBgOver);
rightButton.activeBg = generateBg(st::activeButtonFg);
fillRightButton(
rightButton,
tr::lng_profile_open_app_short(
tr::now,
Ui::Text::WithEntities),
st::dialogRowOpenBot);
return &(_rightButtons.emplace(
user->id,
std::move(rightButton)).first->second);
@ -1459,7 +1497,11 @@ void InnerWidget::paintPeerSearchResult(
context.st->photoSize);
auto nameleft = context.st->nameLeft;
auto namewidth = context.width - nameleft - context.st->padding.right();
auto available = context.width - nameleft - context.st->padding.right();
auto namewidth = available;
if (const auto used = Ui::PaintRightButton(p, context)) {
namewidth -= used - st::dialogsUnreadPadding;
}
QRect rectForName(nameleft, context.st->nameTop, namewidth, st::semiboldFont->height);
if (result->name.isEmpty()) {
@ -1611,7 +1653,7 @@ void InnerWidget::clearIrrelevantState() {
_filteredSelected = -1;
setFilteredPressed(-1, false, false);
_peerSearchSelected = -1;
setPeerSearchPressed(-1);
setPeerSearchPressed(-1, false);
_previewSelected = -1;
setPreviewPressed(-1);
_searchedSelected = -1;
@ -1630,20 +1672,28 @@ bool InnerWidget::lookupIsInBotAppButton(
if (const auto user = MaybeBotWithApp(row)) {
const auto it = _rightButtons.find(user->id);
if (it != _rightButtons.end()) {
const auto s = it->second.bg.size() / style::DevicePixelRatio();
const auto r = QRect(
width() - s.width() - st::dialogRowOpenBotRight,
st::dialogRowOpenBotTop,
s.width(),
s.height());
if (r.contains(localPosition)) {
return true;
}
return lookupIsInRightButton(it->second, localPosition);
}
}
return false;
}
bool InnerWidget::lookupIsInRightButton(
const RightButton &button,
QPoint localPosition) {
if (!button.st) {
return false;
}
const auto s = button.bg.size() / style::DevicePixelRatio();
const auto r = QRect(
width() - s.width() - button.st->margin.right(),
button.st->margin.top(),
s.width(),
s.height());
return r.contains(localPosition);
}
void InnerWidget::selectByMouse(QPoint globalPosition) {
const auto local = mapFromGlobal(globalPosition);
if (updateReorderPinned(local)) {
@ -1687,16 +1737,16 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
const auto mappedY = selected ? mouseY - offset - selected->top() : 0;
const auto selectedTopicJump = selected
&& selected->lookupIsInTopicJump(local.x(), mappedY);
const auto selectedBotApp = selected
const auto selectedRightButton = selected
&& lookupIsInBotAppButton(selected, QPoint(local.x(), mappedY));
if (_collapsedSelected != collapsedSelected
|| _selected != selected
|| _selectedTopicJump != selectedTopicJump
|| _selectedBotApp != selectedBotApp) {
|| _selectedRightButton != selectedRightButton) {
updateSelectedRow();
_selected = selected;
_selectedTopicJump = selectedTopicJump;
_selectedBotApp = selectedBotApp;
_selectedRightButton = selectedRightButton;
_collapsedSelected = collapsedSelected;
updateSelectedRow();
setCursor((_selected || _collapsedSelected >= 0)
@ -1736,29 +1786,39 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
&& _filterResults[filteredSelected].row->lookupIsInTopicJump(
local.x(),
mappedY);
const auto selectedBotApp = (filteredSelected >= 0)
const auto selectedRightButton = (filteredSelected >= 0)
&& lookupIsInBotAppButton(
_filterResults[filteredSelected].row,
QPoint(local.x(), mappedY));
if (_filteredSelected != filteredSelected
|| _selectedTopicJump != selectedTopicJump
|| _selectedBotApp != selectedBotApp) {
|| _selectedRightButton != selectedRightButton) {
updateSelectedRow();
_filteredSelected = filteredSelected;
_selectedTopicJump = selectedTopicJump;
_selectedBotApp = selectedBotApp;
_selectedRightButton = selectedRightButton;
updateSelectedRow();
}
}
if (!_peerSearchResults.empty()) {
auto skip = peerSearchOffset();
const auto skip = peerSearchOffset();
auto peerSearchSelected = (mouseY >= skip) ? ((mouseY - skip) / st::dialogsRowHeight) : -1;
if (peerSearchSelected < 0 || peerSearchSelected >= _peerSearchResults.size()) {
peerSearchSelected = -1;
}
if (_peerSearchSelected != peerSearchSelected) {
const auto mappedY = (peerSearchSelected >= 0)
? mouseY - skip - (peerSearchSelected * st::dialogsRowHeight)
: 0;
const auto selectedRightButton = (peerSearchSelected >= 0)
&& _peerSearchResults[peerSearchSelected]->sponsored
&& lookupIsInRightButton(
_peerSearchResults[peerSearchSelected]->sponsored->button,
QPoint(local.x(), mappedY));
if (_peerSearchSelected != peerSearchSelected
|| _selectedRightButton != selectedRightButton) {
updateSelectedRow();
_peerSearchSelected = peerSearchSelected;
_selectedRightButton = selectedRightButton;
updateSelectedRow();
}
}
@ -1845,12 +1905,15 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
selectByMouse(e->globalPos());
_pressButton = e->button();
setPressed(_selected, _selectedTopicJump, _selectedBotApp);
setPressed(_selected, _selectedTopicJump, _selectedRightButton);
setCollapsedPressed(_collapsedSelected);
setHashtagPressed(_hashtagSelected);
_hashtagDeletePressed = _hashtagDeleteSelected;
setFilteredPressed(_filteredSelected, _selectedTopicJump, _selectedBotApp);
setPeerSearchPressed(_peerSearchSelected);
setFilteredPressed(
_filteredSelected,
_selectedTopicJump,
_selectedRightButton);
setPeerSearchPressed(_peerSearchSelected, _selectedRightButton);
setPreviewPressed(_previewSelected);
setSearchedPressed(_searchedSelected);
_pressedMorePosts = _selectedMorePosts;
@ -1881,7 +1944,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
- QPoint(0, dialogsOffset() + _pressed->top());
if ((_pressButton == Qt::MiddleButton)
&& addQuickActionRipple(row, updateCallback)) {
} else if (addBotAppRipple(origin, updateCallback)) {
} else if (addRightButtonRipple(origin, updateCallback)) {
} else if (_pressedTopicJump) {
row->addTopicJumpRipple(
origin,
@ -1908,7 +1971,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
const auto origin = e->pos()
- QPoint(0, filteredOffset() + result.top);
const auto updateCallback = [=] { repaintDialogRow(filterId, row); };
if (addBotAppRipple(origin, updateCallback)) {
if (addRightButtonRipple(origin, updateCallback)) {
} else if (_pressedTopicJump) {
row->addTopicJumpRipple(
origin,
@ -1923,11 +1986,19 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
}
} else if (base::in_range(_peerSearchPressed, 0, _peerSearchResults.size())) {
auto &result = _peerSearchResults[_peerSearchPressed];
auto row = &result->row;
row->addRipple(
e->pos() - QPoint(0, peerSearchOffset() + _peerSearchPressed * st::dialogsRowHeight),
QSize(width(), st::dialogsRowHeight),
[this, peer = result->peer] { updateSearchResult(peer); });
const auto row = &result->row;
const auto origin = e->pos()
- QPoint(0, peerSearchOffset() + _peerSearchPressed * st::dialogsRowHeight);
const auto updateCallback = [this, peer = result->peer] {
updateSearchResult(peer);
};
if (addRightButtonRipple(origin, updateCallback)) {
} else {
row->addRipple(
origin,
QSize(width(), st::dialogsRowHeight),
updateCallback);
}
} else if (base::in_range(_searchedPressed, 0, _searchResults.size())) {
auto &row = _searchResults[_searchedPressed];
row->addRipple(
@ -1943,22 +2014,22 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
}
}
bool InnerWidget::addBotAppRipple(QPoint origin, Fn<void()> updateCallback) {
if (!(_pressedBotApp && _pressedBotAppData)) {
bool InnerWidget::addRightButtonRipple(QPoint origin, Fn<void()> updateCallback) {
if (!(_pressedRightButton && _pressedRightButtonData)) {
return false;
}
const auto size = _pressedBotAppData->bg.size()
const auto size = _pressedRightButtonData->bg.size()
/ style::DevicePixelRatio();
if (!_pressedBotAppData->ripple) {
_pressedBotAppData->ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
if (!_pressedRightButtonData->ripple) {
_pressedRightButtonData->ripple = std::make_unique<Ui::RippleAnimation>(
_pressedRightButtonData->st->button.ripple,
Ui::RippleAnimation::RoundRectMask(size, size.height() / 2),
std::move(updateCallback));
}
const auto shift = QPoint(
width() - size.width() - st::dialogRowOpenBotRight,
st::dialogRowOpenBotTop);
_pressedBotAppData->ripple->add(origin - shift);
width() - size.width() - _pressedRightButtonData->st->margin.right(),
_pressedRightButtonData->st->margin.top());
_pressedRightButtonData->ripple->add(origin - shift);
return true;
}
@ -2051,7 +2122,7 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
if (!_pressed
|| _dragging
|| (_state != WidgetState::Default)
|| _pressedBotApp) {
|| _pressedRightButtonData) {
return;
} else if (qAbs(localPosition.y() - _dragStart.y())
< style::ConvertScale(kStartReorderThreshold)) {
@ -2321,7 +2392,7 @@ void InnerWidget::mousePressReleased(
setCollapsedPressed(-1);
const auto pressedTopicRootId = _pressedTopicJumpRootId;
const auto pressedTopicJump = _pressedTopicJump;
const auto pressedBotApp = _pressedBotApp;
const auto pressedRightButton = _pressedRightButton;
auto pressed = _pressed;
clearPressed();
auto hashtagPressed = _hashtagPressed;
@ -2331,7 +2402,7 @@ void InnerWidget::mousePressReleased(
auto filteredPressed = _filteredPressed;
setFilteredPressed(-1, false, false);
auto peerSearchPressed = _peerSearchPressed;
setPeerSearchPressed(-1);
setPeerSearchPressed(-1, false);
auto previewPressed = _previewPressed;
setPreviewPressed(-1);
auto searchedPressed = _searchedPressed;
@ -2343,8 +2414,8 @@ void InnerWidget::mousePressReleased(
if (wasDragging) {
selectByMouse(globalPosition);
}
if (_pressedBotAppData && _pressedBotAppData->ripple) {
_pressedBotAppData->ripple->lastStop();
if (_pressedRightButtonData && _pressedRightButtonData->ripple) {
_pressedRightButtonData->ripple->lastStop();
}
if (_activeQuickAction && pressed && !_activeQuickAction->data) {
if (const auto history = pressed->history()) {
@ -2372,13 +2443,14 @@ void InnerWidget::mousePressReleased(
|| (pressed
&& pressed == _selected
&& pressedTopicJump == _selectedTopicJump
&& pressedBotApp == _selectedBotApp)
&& pressedRightButton == _selectedRightButton)
|| (hashtagPressed >= 0
&& hashtagPressed == _hashtagSelected
&& hashtagDeletePressed == _hashtagDeleteSelected)
|| (filteredPressed >= 0 && filteredPressed == _filteredSelected)
|| (peerSearchPressed >= 0
&& peerSearchPressed == _peerSearchSelected)
&& peerSearchPressed == _peerSearchSelected
&& pressedRightButton == _selectedRightButton)
|| (previewPressed >= 0
&& previewPressed == _previewSelected)
|| (searchedPressed >= 0
@ -2387,13 +2459,15 @@ void InnerWidget::mousePressReleased(
&& pressedMorePosts == _selectedMorePosts)
|| (pressedChatTypeFilter
&& pressedChatTypeFilter == _selectedChatTypeFilter)) {
if (pressedBotApp && (pressed || filteredPressed >= 0)) {
if (pressedRightButton && (pressed || filteredPressed >= 0)) {
const auto &row = pressed
? pressed
: _filterResults[filteredPressed].row.get();
if (const auto user = MaybeBotWithApp(row)) {
_openBotMainAppRequests.fire(peerToUser(user->id));
}
} else if (pressedRightButton && peerSearchPressed >= 0) {
showSponsoredMenu(peerSearchPressed, globalPosition);
} else {
chooseRow(modifiers, pressedTopicRootId);
}
@ -2420,25 +2494,25 @@ void InnerWidget::setCollapsedPressed(int pressed) {
void InnerWidget::setPressed(
Row *pressed,
bool pressedTopicJump,
bool pressedBotApp) {
bool pressedRightButton) {
if ((_pressed != pressed)
|| (pressed && _pressedTopicJump != pressedTopicJump)
|| (pressed && _pressedBotApp != pressedBotApp)) {
|| (pressed && _pressedRightButton != pressedRightButton)) {
if (_pressed) {
_pressed->stopLastRipple();
}
if (_pressedBotAppData && _pressedBotAppData->ripple) {
_pressedBotAppData->ripple->lastStop();
if (_pressedRightButtonData && _pressedRightButtonData->ripple) {
_pressedRightButtonData->ripple->lastStop();
}
_pressed = pressed;
if (pressed || !pressedTopicJump || !pressedBotApp) {
if (pressed || !pressedTopicJump || !pressedRightButton) {
_pressedTopicJump = pressedTopicJump;
_pressedBotApp = pressedBotApp;
if (pressedBotApp) {
_pressedRightButton = pressedRightButton;
if (pressedRightButton) {
if (const auto user = MaybeBotWithApp(pressed)) {
const auto it = _rightButtons.find(user->id);
if (it != _rightButtons.end()) {
_pressedBotAppData = &(it->second);
_pressedRightButtonData = &(it->second);
}
}
}
@ -2465,26 +2539,26 @@ void InnerWidget::setHashtagPressed(int pressed) {
void InnerWidget::setFilteredPressed(
int pressed,
bool pressedTopicJump,
bool pressedBotApp) {
bool pressedRightButton) {
if (_filteredPressed != pressed
|| (pressed >= 0 && _pressedTopicJump != pressedTopicJump)
|| (pressed >= 0 && _pressedBotApp != pressedBotApp)) {
|| (pressed >= 0 && _pressedRightButton != pressedRightButton)) {
if (base::in_range(_filteredPressed, 0, _filterResults.size())) {
_filterResults[_filteredPressed].row->stopLastRipple();
}
if (_pressedBotAppData && _pressedBotAppData->ripple) {
_pressedBotAppData->ripple->lastStop();
if (_pressedRightButtonData && _pressedRightButtonData->ripple) {
_pressedRightButtonData->ripple->lastStop();
}
_filteredPressed = pressed;
if (pressed >= 0 || !pressedTopicJump || !pressedBotApp) {
if (pressed >= 0 || !pressedTopicJump || !pressedRightButton) {
_pressedTopicJump = pressedTopicJump;
_pressedBotApp = pressedBotApp;
if (pressed >= 0 && pressedBotApp) {
_pressedRightButton = pressedRightButton;
if (pressed >= 0 && pressedRightButton) {
const auto &row = _filterResults[pressed].row;
if (const auto history = row->history()) {
const auto it = _rightButtons.find(history->peer->id);
if (it != _rightButtons.end()) {
_pressedBotAppData = &(it->second);
_pressedRightButtonData = &(it->second);
}
}
}
@ -2497,11 +2571,26 @@ void InnerWidget::setFilteredPressed(
}
}
void InnerWidget::setPeerSearchPressed(int pressed) {
if (base::in_range(_peerSearchPressed, 0, _peerSearchResults.size())) {
_peerSearchResults[_peerSearchPressed]->row.stopLastRipple();
void InnerWidget::setPeerSearchPressed(int pressed, bool pressedRightButton) {
if (_peerSearchPressed != pressed
|| (pressed >= 0 && _pressedRightButton != pressedRightButton)) {
if (base::in_range(_peerSearchPressed, 0, _peerSearchResults.size())) {
_peerSearchResults[_peerSearchPressed]->row.stopLastRipple();
}
if (_pressedRightButtonData && _pressedRightButtonData->ripple) {
_pressedRightButtonData->ripple->lastStop();
}
_peerSearchPressed = pressed;
if (pressed >= 0 || !pressedRightButton) {
_pressedRightButton = pressedRightButton;
if (pressed >= 0 && pressedRightButton) {
const auto &entry = _peerSearchResults[pressed];
if (entry->sponsored) {
_pressedRightButtonData = &entry->sponsored->button;
}
}
}
}
_peerSearchPressed = pressed;
}
void InnerWidget::setPreviewPressed(int pressed) {
@ -2564,7 +2653,7 @@ void InnerWidget::dialogRowReplaced(
_selected = newRow;
}
if (_pressed == oldRow) {
setPressed(newRow, _pressedTopicJump, _pressedBotApp);
setPressed(newRow, _pressedTopicJump, _pressedRightButton);
}
if (_dragging == oldRow) {
if (newRow) {
@ -3081,6 +3170,62 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
}
}
void InnerWidget::showSponsoredMenu(int peerSearchIndex, QPoint globalPos) {
_menu = nullptr;
const auto count = int(_peerSearchResults.size());
const auto entry = (peerSearchIndex >= 0 && peerSearchIndex < count)
? _peerSearchResults[peerSearchIndex].get()
: nullptr;
if (!entry || !entry->sponsored) {
return;
}
_peerSearchMenu = peerSearchIndex;
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
st::popupMenuExpandedSeparator);
const auto peer = entry->peer;
const auto remove = crl::guard(this, [=] {
_sponsoredRemoved.emplace(peer);
_peerSearchResults.erase(
ranges::remove(
_peerSearchResults,
peer,
&PeerSearchResult::peer),
end(_peerSearchResults));
refresh();
});
Menu::FillSponsored(
this,
Ui::Menu::CreateAddActionCallback(_menu),
_controller->uiShow(),
Menu::SponsoredPhrases::Search,
session().sponsoredMessages().lookupDetails(entry->sponsored->data),
session().sponsoredMessages().createReportCallback(
entry->sponsored->data.randomId,
remove),
false,
false);
QObject::connect(_menu.get(), &QObject::destroyed, [=] {
if (_peerSearchMenu >= 0
&& _peerSearchMenu < _peerSearchResults.size()) {
const auto index = std::exchange(_peerSearchMenu, -1);
updateSearchResult(_peerSearchResults[index]->peer);
}
const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) {
setMouseTracking(true);
selectByMouse(globalPosition);
}
});
if (_menu->empty()) {
_menu = nullptr;
} else {
_menu->popup(globalPos);
}
}
void InnerWidget::parentGeometryChanged() {
const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) {
@ -3362,14 +3507,23 @@ InnerWidget::~InnerWidget() {
clearSearchResults();
}
void InnerWidget::clearSearchResults(bool clearPeerSearchResults) {
if (clearPeerSearchResults) {
_peerSearchResults.clear();
void InnerWidget::clearSearchResults(bool alsoPeerSearchResults) {
if (alsoPeerSearchResults) {
clearPeerSearchResults();
}
_searchResults.clear();
_searchedCount = _searchedMigratedCount = 0;
}
void InnerWidget::clearPeerSearchResults() {
_peerSearchResults.clear();
if (_pressedRightButtonSponsored) {
_pressedRightButtonData = nullptr;
_pressedRightButtonSponsored = false;
_pressedRightButton = false;
}
}
void InnerWidget::clearPreviewResults() {
_previewResults.clear();
_previewCount = 0;
@ -3691,7 +3845,7 @@ void InnerWidget::peerSearchReceived(Api::PeerSearchResult result) {
}
_peerSearchQuery = result.query.toLower().trimmed();
_peerSearchResults.clear();
clearPeerSearchResults();
_peerSearchResults.reserve(result.peers.size()
+ result.sponsored.size());
for (const auto &peer : result.my) {
@ -3706,14 +3860,17 @@ void InnerWidget::peerSearchReceived(Api::PeerSearchResult result) {
};
auto added = base::flat_set<not_null<PeerData*>>();
for (const auto &sponsored : result.sponsored) {
if (inlist(sponsored.peer)) {
const auto peer = sponsored.peer;
if (inlist(peer) || _sponsoredRemoved.contains(peer)) {
continue;
}
_peerSearchResults.push_back(
std::make_unique<PeerSearchResult>(sponsored.peer));
std::make_unique<PeerSearchResult>(peer));
_peerSearchResults.back()->sponsored
= std::make_unique<Api::SponsoredSearchResult>(sponsored);
added.emplace(sponsored.peer);
= std::make_unique<SponsoredSearchResult>(SponsoredSearchResult{
.data = sponsored,
});
added.emplace(peer);
}
for (const auto &peer : result.peers) {
if (added.contains(peer) || inlist(peer)) {
@ -4054,7 +4211,7 @@ void InnerWidget::clearFilter() {
_hashtagResults.clear();
_filterResults.clear();
_filterResultsGlobal.clear();
_peerSearchResults.clear();
clearPeerSearchResults();
_searchResults.clear();
_previewResults.clear();
_trackedHistories.clear();
@ -4515,7 +4672,7 @@ ChosenRow InnerWidget::computeChosenRow() const {
.key = session().data().history(row->peer),
.message = Data::UnreadMessagePosition,
.sponsoredRandomId = (row->sponsored
? row->sponsored->randomId
? row->sponsored->data.randomId
: QByteArray()),
};
} else if (base::in_range(_previewSelected, 0, _previewResults.size())) {

View file

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace style {
struct DialogRow;
struct DialogRightButton;
} // namespace style
namespace Api {
@ -242,6 +243,7 @@ protected:
private:
struct CollapsedRow;
struct HashtagResult;
struct SponsoredSearchResult;
struct PeerSearchResult;
struct TagCache;
@ -299,6 +301,7 @@ private:
void repaintDialogRow(RowDescriptor row);
void refreshDialogRow(RowDescriptor row);
bool updateEntryHeight(not_null<Entry*> entry);
void showSponsoredMenu(int peerSearchIndex, QPoint globalPos);
void clearMouseSelection(bool clearSelection = false);
void mousePressReleased(
@ -312,14 +315,17 @@ private:
void scrollToItem(int top, int height);
void scrollToDefaultSelected();
void setCollapsedPressed(int pressed);
void setPressed(Row *pressed, bool pressedTopicJump, bool pressedBotApp);
void setPressed(
Row *pressed,
bool pressedTopicJump,
bool pressedRightButton);
void clearPressed();
void setHashtagPressed(int pressed);
void setFilteredPressed(
int pressed,
bool pressedTopicJump,
bool pressedBotApp);
void setPeerSearchPressed(int pressed);
bool pressedRightButton);
void setPeerSearchPressed(int pressed, bool pressedRightButton);
void setPreviewPressed(int pressed);
void setSearchedPressed(int pressed);
bool isPressed() const {
@ -357,6 +363,8 @@ private:
bool addBotAppRipple(QPoint origin, Fn<void()> updateCallback);
bool addQuickActionRipple(not_null<Row*> row, Fn<void()> updateCallback);
bool addRightButtonRipple(QPoint origin, Fn<void()> updateCallback);
void setupShortcuts();
RowDescriptor computeJump(
const RowDescriptor &to,
@ -459,7 +467,8 @@ private:
Ui::VideoUserpic *validateVideoUserpic(not_null<History*> history);
Row *shownRowByKey(Key key);
void clearSearchResults(bool clearPeerSearchResults = true);
void clearSearchResults(bool alsoPeerSearchResults = true);
void clearPeerSearchResults();
void clearPreviewResults();
void updateSelectedRow(Key key = Key());
void trackResultsHistory(not_null<History*> history);
@ -493,7 +502,14 @@ private:
[[nodiscard]] bool lookupIsInBotAppButton(
Row *row,
QPoint localPosition);
[[nodiscard]] bool lookupIsInRightButton(
const RightButton &button,
QPoint localPosition);
[[nodiscard]] RightButton *maybeCacheRightButton(Row *row);
void fillRightButton(
RightButton &button,
const TextWithEntities &text,
const style::DialogRightButton &st);
[[nodiscard]] QImage *cacheChatsFilterTag(
const Data::ChatFilter &filter,
@ -529,9 +545,10 @@ private:
bool _selectedTopicJump = false;
bool _pressedTopicJump = false;
RightButton *_pressedBotAppData = nullptr;
bool _selectedBotApp = false;
bool _pressedBotApp = false;
RightButton *_pressedRightButtonData = nullptr;
bool _pressedRightButtonSponsored = false;
bool _selectedRightButton = false;
bool _pressedRightButton = false;
Row *_dragging = nullptr;
int _draggingIndex = -1;
@ -566,9 +583,11 @@ private:
rpl::lifetime _trackedLifetime;
QString _peerSearchQuery;
base::flat_set<not_null<PeerData*>> _sponsoredRemoved;
std::vector<std::unique_ptr<PeerSearchResult>> _peerSearchResults;
int _peerSearchSelected = -1;
int _peerSearchPressed = -1;
int _peerSearchMenu = -1;
std::vector<std::unique_ptr<FakeRow>> _previewResults;
int _previewCount = 0;

View file

@ -835,7 +835,10 @@ void Widget::setupSwipeBack() {
void Widget::chosenRow(const ChosenRow &row) {
storiesToggleExplicitExpand(false);
if (!_searchState.query.isEmpty()) {
if (!row.sponsoredRandomId.isEmpty()) {
auto &messages = session().sponsoredMessages();
messages.clicked(row.sponsoredRandomId, false, false);
} else if (!_searchState.query.isEmpty()) {
if (const auto history = row.key.history()) {
session().recentPeers().bump(history->peer);
}
@ -846,11 +849,6 @@ void Widget::chosenRow(const ChosenRow &row) {
? history->peer->forumTopicFor(row.message.fullId.msg)
: nullptr;
if (!row.sponsoredRandomId.isEmpty()) {
auto &messages = session().sponsoredMessages();
messages.clicked(row.sponsoredRandomId, false, false);
}
if (topicJump) {
if (controller()->shownForum().current() == topicJump->forum()) {
controller()->closeForum();

View file

@ -125,16 +125,18 @@ void PaintRowTopRight(
text);
}
int PaintRightButton(QPainter &p, const PaintContext &context) {
int PaintRightButtonImpl(QPainter &p, const PaintContext &context) {
if (context.width < st::columnMinimalWidthLeft) {
return 0;
}
if (const auto rightButton = context.rightButton) {
Assert(rightButton->st != nullptr);
const auto size = rightButton->bg.size() / style::DevicePixelRatio();
const auto left = context.width
- size.width()
- st::dialogRowOpenBotRight;
const auto top = st::dialogRowOpenBotTop;
- rightButton->st->margin.right();
const auto top = rightButton->st->margin.top();
p.drawImage(
left,
top,
@ -149,22 +151,22 @@ int PaintRightButton(QPainter &p, const PaintContext &context) {
left,
top,
size.width() - size.height() / 2,
context.active
(context.active
? &st::universalRippleAnimation.color->c
: &st::activeButtonBgRipple->c);
: &rightButton->st->button.ripple.color->c));
if (rightButton->ripple->empty()) {
rightButton->ripple.reset();
}
}
p.setPen(context.active
? st::activeButtonBg
? rightButton->st->button.textBg
: context.selected
? st::activeButtonFgOver
: st::activeButtonFg);
? rightButton->st->button.textFgOver
: rightButton->st->button.textFg);
rightButton->text.draw(p, {
.position = QPoint(
left + size.height() / 2,
top + (st::dialogRowOpenBotHeight - rightButton->text.minHeight()) / 2),
top + rightButton->st->button.textTop),
.outerWidth = size.width() - size.height() / 2,
.availableWidth = size.width() - size.height() / 2,
.elisionLines = 1,
@ -1285,4 +1287,8 @@ void PaintCollapsedRow(
}
}
int PaintRightButton(QPainter &p, const PaintContext &context) {
return PaintRightButtonImpl(p, context);
}
} // namespace Dialogs::Ui

View file

@ -110,4 +110,6 @@ void PaintCollapsedRow(
int unread,
const PaintContext &context);
int PaintRightButton(QPainter &p, const PaintContext &context);
} // namespace Dialogs::Ui

View file

@ -204,7 +204,7 @@ RecentRow::RecentRow(not_null<PeerData*> peer)
if (const auto user = peer->asUser()) {
if (user->botInfo && user->botInfo->hasMainApp) {
return std::make_unique<Ui::Text::String>(
st::dialogRowOpenBotTextStyle,
st::dialogRowOpenBotRecent.button.style,
tr::lng_profile_open_app_short(tr::now));
}
}
@ -275,20 +275,15 @@ QSize RecentRow::rightActionSize() const {
if (_mainAppText && _badgeSize.isEmpty()) {
return QSize(
_mainAppText->maxWidth() + _mainAppText->minHeight(),
st::dialogRowOpenBotHeight);
st::dialogRowOpenBotRecent.button.height);
}
return _badgeSize;
}
QMargins RecentRow::rightActionMargins() const {
if (_mainAppText && _badgeSize.isEmpty()) {
return QMargins(
0,
st::dialogRowOpenBotRecentTop,
st::dialogRowOpenBotRight,
0);
}
if (_badgeSize.isEmpty()) {
return st::dialogRowOpenBotRecent.margin;
} else if (_badgeSize.isEmpty()) {
return {};
}
const auto x = st::recentPeersItem.photoPosition.x();
@ -321,8 +316,7 @@ void RecentRow::rightActionPaint(
p.setPen(actionSelected
? st::activeButtonFgOver
: st::activeButtonFg);
const auto top = 0
+ (st::dialogRowOpenBotHeight - _mainAppText->minHeight()) / 2;
const auto top = st::dialogRowOpenBotRecent.button.textTop;
_mainAppText->draw(p, {
.position = QPoint(x + size.height() / 2, y + top),
.outerWidth = outerWidth,

View file

@ -699,7 +699,8 @@ ClickHandlerPtr HideSponsoredClickHandler() {
if (session.premium()) {
using Result = Data::SponsoredReportResult;
session.sponsoredMessages().createReportCallback(
my.itemId)(Result::Id("-1"), [](const auto &) {});
my.itemId
).callback(Result::Id("-1"), [](const auto &) {});
} else {
ShowPremiumPreviewBox(controller, PremiumFeature::NoAds);
}

View file

@ -116,7 +116,7 @@ channelEarnFadeDuration: 60;
channelEarnLearnDescription: FlatLabel(defaultFlatLabel) {
maxHeight: 0px;
minWidth: 280px;
minWidth: 264px;
align: align(top);
}

View file

@ -42,15 +42,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Menu {
namespace {
[[nodiscard]] SponsoredPhrases PhrasesForMessage(FullMsgId fullId) {
return peerIsChannel(fullId.peer)
? SponsoredPhrases::Channel
: SponsoredPhrases::Bot;
}
void AboutBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId) {
SponsoredPhrases phrases,
const Data::SponsoredMessages::Details &details,
Data::SponsoredReportAction report) {
constexpr auto kUrl = "https://promote.telegram.org"_cs;
box->setNoContentMargin(true);
box->setWidth(st::boxWideWidth);
const auto isChannel = peerIsChannel(fullId.peer);
const auto isChannel = (phrases == SponsoredPhrases::Channel);
const auto isSearch = (phrases == SponsoredPhrases::Search);
const auto session = &show->session();
const auto content = box->verticalLayout().get();
@ -138,20 +147,24 @@ void AboutBox(
tr::lng_sponsored_revenued_info1_title(),
(isChannel
? tr::lng_sponsored_revenued_info1_description
: isSearch
? tr::lng_sponsored_revenued_info1_search_description
: tr::lng_sponsored_revenued_info1_bot_description)(
Ui::Text::RichLangValue),
st::sponsoredAboutPrivacyIcon);
Ui::AddSkip(content);
Ui::AddSkip(content);
addEntry(
(isChannel
? tr::lng_sponsored_revenued_info2_title
: tr::lng_sponsored_revenued_info2_bot_title)(),
(isChannel
? tr::lng_sponsored_revenued_info2_description
: tr::lng_sponsored_revenued_info2_bot_description)(
Ui::Text::RichLangValue),
st::sponsoredAboutSplitIcon);
if (!isSearch) {
Ui::AddSkip(content);
Ui::AddSkip(content);
addEntry(
(isChannel
? tr::lng_sponsored_revenued_info2_title
: tr::lng_sponsored_revenued_info2_bot_title)(),
(isChannel
? tr::lng_sponsored_revenued_info2_description
: tr::lng_sponsored_revenued_info2_bot_description)(
Ui::Text::RichLangValue),
st::sponsoredAboutSplitIcon);
}
Ui::AddSkip(content);
Ui::AddSkip(content);
auto link = tr::lng_settings_privacy_premium_link(
@ -160,17 +173,32 @@ void AboutBox(
});
addEntry(
tr::lng_sponsored_revenued_info3_title(),
isChannel
(isChannel
? tr::lng_sponsored_revenued_info3_description(
lt_count,
rpl::single(float64(levels)),
lt_link,
std::move(link),
Ui::Text::RichLangValue)
: isSearch
? tr::lng_sponsored_revenued_info3_search_description(
lt_link,
tr::lng_sponsored_revenued_info3_search_link(
lt_arrow,
rpl::single(
Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
Ui::Text::WithEntities
) | rpl::map([](TextWithEntities &&link) {
return Ui::Text::Wrapped(
std::move(link),
EntityType::CustomUrl,
u"internal:"_q);
}),
Ui::Text::RichLangValue)
: tr::lng_sponsored_revenued_info3_bot_description(
lt_link,
std::move(link),
Ui::Text::RichLangValue),
Ui::Text::RichLangValue)),
st::sponsoredAboutRemoveIcon)->setClickHandlerFilter([=](
const auto &...) {
ShowPremiumPreviewBox(show, PremiumFeature::NoAds);
@ -200,6 +228,8 @@ void AboutBox(
content,
(isChannel
? tr::lng_sponsored_revenued_footer_description
: isSearch
? tr::lng_sponsored_revenued_footer_search_description
: tr::lng_sponsored_revenued_footer_bot_description)(
lt_link,
tr::lng_channel_earn_about_link(
@ -256,7 +286,9 @@ void AboutBox(
top,
Ui::Menu::CreateAddActionCallback(menu->get()),
show,
fullId,
phrases,
details,
report,
false,
true);
const auto global = top->mapToGlobal(
@ -269,14 +301,11 @@ void AboutBox(
return true;
});
}
}
void ShowReportSponsoredBox(
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId) {
auto &sponsoredMessages = show->session().sponsoredMessages();
const auto report = sponsoredMessages.createReportCallback(fullId);
Data::SponsoredReportAction report) {
const auto guideLink = Ui::Text::Link(
tr::lng_report_sponsored_reported_link(tr::now),
u"https://promote.telegram.org/guidelines"_q);
@ -284,7 +313,7 @@ void ShowReportSponsoredBox(
auto performRequest = [=](
const auto &repeatRequest,
Data::SponsoredReportResult::Id id) -> void {
report(id, [=](const Data::SponsoredReportResult &result) {
report.callback(id, [=](const Data::SponsoredReportResult &result) {
if (!result.error.isEmpty()) {
show->showToast(result.error);
}
@ -360,11 +389,12 @@ void FillSponsored(
not_null<Ui::RpWidget*> parent,
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId,
SponsoredPhrases phrases,
const Data::SponsoredMessages::Details &details,
Data::SponsoredReportAction report,
bool mediaViewer,
bool skipAbout) {
const auto session = &show->session();
const auto details = session->sponsoredMessages().lookupDetails(fullId);
const auto &info = details.info;
if (!mediaViewer && !info.empty()) {
@ -408,12 +438,12 @@ void FillSponsored(
if (details.canReport) {
if (!skipAbout) {
addAction(tr::lng_sponsored_menu_revenued_about(tr::now), [=] {
show->show(Box(AboutBox, show, fullId));
show->show(Box(AboutBox, show, phrases, details, report));
}, (mediaViewer ? &st::mediaMenuIconInfo : &st::menuIconInfo));
}
addAction(tr::lng_sponsored_menu_revenued_report(tr::now), [=] {
ShowReportSponsoredBox(show, fullId);
ShowReportSponsoredBox(show, report);
}, (mediaViewer ? &st::mediaMenuIconBlock : &st::menuIconBlock));
addAction({
@ -426,14 +456,32 @@ void FillSponsored(
addAction(tr::lng_sponsored_hide_ads(tr::now), [=] {
if (session->premium()) {
using Result = Data::SponsoredReportResult;
session->sponsoredMessages().createReportCallback(
fullId)(Result::Id("-1"), [](const auto &) {});
report.callback(Result::Id("-1"), [](const auto &) {});
} else {
ShowPremiumPreviewBox(show, PremiumFeature::NoAds);
}
}, (mediaViewer ? &st::mediaMenuIconCancel : &st::menuIconCancel));
}
void FillSponsored(
not_null<Ui::RpWidget*> parent,
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId,
bool mediaViewer,
bool skipAbout) {
const auto session = &show->session();
FillSponsored(
parent,
addAction,
show,
PhrasesForMessage(fullId),
session->sponsoredMessages().lookupDetails(fullId),
session->sponsoredMessages().createReportCallback(fullId),
mediaViewer,
skipAbout);
}
void ShowSponsored(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
@ -455,8 +503,14 @@ void ShowSponsored(
void ShowSponsoredAbout(
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId) {
const auto session = &show->session();
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
AboutBox(box, show, fullId);
AboutBox(
box,
show,
PhrasesForMessage(fullId),
session->sponsoredMessages().lookupDetails(fullId),
session->sponsoredMessages().createReportCallback(fullId));
}));
}

View file

@ -11,6 +11,11 @@ namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data {
struct SponsoredMessageDetails;
struct SponsoredReportAction;
} // namespace Data
namespace Ui {
class RpWidget;
namespace Menu {
@ -22,6 +27,22 @@ class HistoryItem;
namespace Menu {
enum class SponsoredPhrases {
Channel,
Bot,
Search,
};
void FillSponsored(
not_null<Ui::RpWidget*> parent,
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
SponsoredPhrases phrases,
const Data::SponsoredMessageDetails &details,
Data::SponsoredReportAction report,
bool mediaViewer,
bool skipAbout);
void FillSponsored(
not_null<Ui::RpWidget*> parent,
const Ui::Menu::MenuCallback &addAction,