/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "menu/menu_sponsored.h" #include "boxes/premium_preview_box.h" #include "chat_helpers/compose/compose_show.h" #include "data/components/sponsored_messages.h" #include "data/data_premium_limits.h" #include "data/data_session.h" #include "data/stickers/data_custom_emoji.h" #include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/boxes/report_box_graphics.h" // AddReportOptionButton. #include "ui/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/widgets/label_with_custom_emoji.h" #include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_multiline_action.h" #include "ui/widgets/popup_menu.h" #include "styles/style_channel_earn.h" #include "styles/style_chat.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_media_view.h" #include "styles/style_menu_icons.h" #include "styles/style_premium.h" #include "styles/style_settings.h" namespace Menu { namespace { void AboutBox( not_null box, std::shared_ptr show, const FullMsgId &fullId) { constexpr auto kUrl = "https://promote.telegram.org"_cs; box->setNoContentMargin(true); const auto isChannel = peerIsChannel(fullId.peer); const auto session = &show->session(); const auto content = box->verticalLayout().get(); const auto levels = Data::LevelLimits(session) .channelRestrictSponsoredLevelMin(); Ui::AddSkip(content); Ui::AddSkip(content); Ui::AddSkip(content); { const auto &icon = st::sponsoredAboutTitleIcon; const auto rect = Rect(icon.size() * 1.4); auto owned = object_ptr(content); owned->resize(rect.size()); const auto widget = box->addRow(object_ptr>( content, std::move(owned)))->entity(); widget->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(widget); auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); p.setBrush(st::activeButtonBg); p.drawEllipse(rect); icon.paintInCenter(p, rect); }, widget->lifetime()); } Ui::AddSkip(content); Ui::AddSkip(content); box->addRow(object_ptr>( content, object_ptr( content, tr::lng_sponsored_menu_revenued_about(), st::boxTitle))); Ui::AddSkip(content); box->addRow(object_ptr>( content, object_ptr( content, tr::lng_sponsored_revenued_subtitle(), st::channelEarnLearnDescription))); Ui::AddSkip(content); Ui::AddSkip(content); { const auto padding = QMargins( st::settingsButton.padding.left(), st::boxRowPadding.top(), st::boxRowPadding.right(), st::boxRowPadding.bottom()); const auto addEntry = [&]( rpl::producer title, rpl::producer about, const style::icon &icon) { const auto top = content->add( object_ptr( content, std::move(title), st::channelEarnSemiboldLabel), padding); Ui::AddSkip(content, st::channelEarnHistoryThreeSkip); const auto label = content->add( object_ptr( content, std::move(about), st::channelEarnHistoryRecipientLabel), padding); const auto left = Ui::CreateChild( box->verticalLayout().get()); left->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(left); icon.paint(p, 0, 0, left->width()); }, left->lifetime()); left->resize(icon.size()); top->geometryValue( ) | rpl::start_with_next([=](const QRect &g) { left->moveToLeft( (g.left() - left->width()) / 2, g.top() + st::channelEarnHistoryThreeSkip); }, left->lifetime()); return label; }; addEntry( tr::lng_sponsored_revenued_info1_title(), (isChannel ? tr::lng_sponsored_revenued_info1_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); Ui::AddSkip(content); Ui::AddSkip(content); auto link = tr::lng_settings_privacy_premium_link( ) | rpl::map([](QString t) { return Ui::Text::Link(std::move(t), u"internal:"_q); }); addEntry( tr::lng_sponsored_revenued_info3_title(), isChannel ? tr::lng_sponsored_revenued_info3_description( lt_count, rpl::single(float64(levels)), lt_link, std::move(link), Ui::Text::RichLangValue) : tr::lng_sponsored_revenued_info3_bot_description( lt_link, std::move(link), Ui::Text::RichLangValue), st::sponsoredAboutRemoveIcon)->setClickHandlerFilter([=]( const auto &...) { ShowPremiumPreviewBox(show, PremiumFeature::NoAds); return true; }); Ui::AddSkip(content); Ui::AddSkip(content); } Ui::AddSkip(content); Ui::AddSkip(content); { box->addRow( object_ptr>( content, object_ptr( content, tr::lng_sponsored_revenued_footer_title(), st::boxTitle))); } Ui::AddSkip(content); { const auto arrow = Ui::Text::SingleCustomEmoji( session->data().customEmojiManager().registerInternalEmoji( st::topicButtonArrow, st::channelEarnLearnArrowMargins, true)); const auto available = box->width() - rect::m::sum::h(st::boxRowPadding); box->addRow( Ui::CreateLabelWithCustomEmoji( content, (isChannel ? tr::lng_sponsored_revenued_footer_description : tr::lng_sponsored_revenued_footer_bot_description)( lt_link, tr::lng_channel_earn_about_link( lt_emoji, rpl::single(arrow), Ui::Text::RichLangValue ) | rpl::map([=](TextWithEntities t) { return Ui::Text::Link(std::move(t), kUrl.utf16()); }), Ui::Text::RichLangValue), { .session = session }, st::channelEarnLearnDescription))->resizeToWidth(available); } Ui::AddSkip(content); Ui::AddSkip(content); { const auto &st = st::premiumPreviewDoubledLimitsBox; box->setStyle(st); auto button = object_ptr( box, tr::lng_box_ok(), st::defaultActiveButton); button->resizeToWidth(box->width() - st.buttonPadding.left() - st.buttonPadding.left()); button->setClickedCallback([=] { box->closeBox(); }); box->addButton(std::move(button)); } if (!isChannel) { const auto top = Ui::CreateChild( box, st::infoTopBarMenu); box->widthValue( ) | rpl::start_with_next([=](int width) { top->raise(); top->moveToLeft( width - top->width() - st::defaultScrollArea.width, 0); }, top->lifetime()); using MenuPtr = base::unique_qptr; const auto menu = top->lifetime().make_state(); top->setClickedCallback([=] { *menu = base::make_unique_q( box->window(), st::popupMenuWithIcons); const auto raw = menu->get(); raw->animatePhaseValue( ) | rpl::start_with_next([=](Ui::PopupMenu::AnimatePhase phase) { top->setForceRippled(phase == Ui::PopupMenu::AnimatePhase::Shown || phase == Ui::PopupMenu::AnimatePhase::StartShow); }, top->lifetime()); FillSponsored( top, Ui::Menu::CreateAddActionCallback(menu->get()), show, fullId, false, true); const auto global = top->mapToGlobal( QPoint(top->width() / 4 * 3, top->height() / 2)); raw->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight); raw->popup( QPoint( global.x(), std::max(global.y(), QCursor::pos().y()))); return true; }); } } void ShowReportSponsoredBox( std::shared_ptr show, const FullMsgId &fullId) { auto &sponsoredMessages = show->session().sponsoredMessages(); const auto report = sponsoredMessages.createReportCallback(fullId); const auto guideLink = Ui::Text::Link( tr::lng_report_sponsored_reported_link(tr::now), u"https://promote.telegram.org/guidelines"_q); auto performRequest = [=]( const auto &repeatRequest, Data::SponsoredReportResult::Id id) -> void { report(id, [=](const Data::SponsoredReportResult &result) { if (!result.error.isEmpty()) { show->showToast(result.error); } if (!result.options.empty()) { show->show(Box([=](not_null box) { box->setTitle(rpl::single(result.title)); for (const auto &option : result.options) { const auto button = Ui::AddReportOptionButton( box->verticalLayout(), option.text, nullptr); button->setClickedCallback([=] { repeatRequest(repeatRequest, option.id); }); } if (!id.isNull()) { box->addLeftButton( tr::lng_create_group_back(), [=] { box->closeBox(); }); } else { const auto container = box->verticalLayout(); Ui::AddSkip(container); container->add(object_ptr( container, object_ptr( container, tr::lng_report_sponsored_reported_learn( lt_link, rpl::single(guideLink), Ui::Text::WithEntities), st::boxDividerLabel), st::defaultBoxDividerLabelPadding, RectPart::Top | RectPart::Bottom)); } box->addButton( tr::lng_close(), [=] { show->hideLayer(); }); })); } else { constexpr auto kToastDuration = crl::time(4000); switch (result.result) { case Data::SponsoredReportResult::FinalStep::Hidden: { show->showToast( tr::lng_report_sponsored_hidden(tr::now), kToastDuration); } break; case Data::SponsoredReportResult::FinalStep::Reported: { auto text = tr::lng_report_sponsored_reported( tr::now, lt_link, guideLink, Ui::Text::WithEntities); show->showToast({ .text = std::move(text), .duration = kToastDuration, }); } break; case Data::SponsoredReportResult::FinalStep::Premium: { ShowPremiumPreviewBox(show, PremiumFeature::NoAds); } break; } show->hideLayer(); } }); }; performRequest(performRequest, Data::SponsoredReportResult::Id()); } } // namespace void FillSponsored( not_null parent, const Ui::Menu::MenuCallback &addAction, std::shared_ptr show, const FullMsgId &fullId, 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()) { auto fillSubmenu = [&](not_null menu) { const auto allText = ranges::accumulate( info, TextWithEntities(), [](TextWithEntities a, TextWithEntities b) { return a.text.isEmpty() ? b : a.append('\n').append(b); }).text; const auto callback = [=] { TextUtilities::SetClipboardText({ allText }); show->showToast(tr::lng_text_copied(tr::now)); }; for (const auto &i : info) { auto item = base::make_unique_q( menu, st::defaultMenu, st::historySponsorInfoItem, st::historyHasCustomEmojiPosition, base::duplicate(i)); item->clicks( ) | rpl::start_with_next(callback, menu->lifetime()); menu->addAction(std::move(item)); if (i != details.info.back()) { menu->addSeparator(); } } }; addAction({ .text = tr::lng_sponsored_info_menu(tr::now), .handler = nullptr, .icon = &st::menuIconChannel, .fillSubmenu = std::move(fillSubmenu), }); addAction({ .separatorSt = &st::expandedMenuSeparator, .isSeparator = true, }); } if (details.canReport) { if (!skipAbout) { addAction(tr::lng_sponsored_menu_revenued_about(tr::now), [=] { show->show(Box(AboutBox, show, fullId)); }, (mediaViewer ? &st::mediaMenuIconInfo : &st::menuIconInfo)); } addAction(tr::lng_sponsored_menu_revenued_report(tr::now), [=] { ShowReportSponsoredBox(show, fullId); }, (mediaViewer ? &st::mediaMenuIconBlock : &st::menuIconBlock)); addAction({ .separatorSt = (mediaViewer ? &st::mediaviewMenuSeparator : &st::expandedMenuSeparator), .isSeparator = true, }); } addAction(tr::lng_sponsored_hide_ads(tr::now), [=] { if (session->premium()) { using Result = Data::SponsoredReportResult; session->sponsoredMessages().createReportCallback( fullId)(Result::Id("-1"), [](const auto &) {}); } else { ShowPremiumPreviewBox(show, PremiumFeature::NoAds); } }, (mediaViewer ? &st::mediaMenuIconCancel : &st::menuIconCancel)); } void ShowSponsored( not_null parent, std::shared_ptr show, const FullMsgId &fullId) { const auto menu = Ui::CreateChild( parent.get(), st::popupMenuWithIcons); FillSponsored( parent, Ui::Menu::CreateAddActionCallback(menu), show, fullId, false); menu->popup(QCursor::pos()); } void ShowSponsoredAbout( std::shared_ptr show, const FullMsgId &fullId) { show->showBox(Box([=](not_null box) { AboutBox(box, show, fullId); })); } } // namespace Menu