From 0d821c3630d649b72576175dfd8b4f2ca82da551 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Dec 2024 18:08:00 +0400 Subject: [PATCH] Implement simple bot verification management. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 41 ++- .../boxes/peers/edit_peer_info_box.cpp | 36 +++ .../boxes/peers/verify_peers_box.cpp | 300 ++++++++++++++++++ .../boxes/peers/verify_peers_box.h | 38 +++ Telegram/SourceFiles/data/data_session.cpp | 9 +- Telegram/SourceFiles/data/data_session.h | 4 +- Telegram/SourceFiles/data/data_user.cpp | 47 ++- Telegram/SourceFiles/data/data_user.h | 15 + Telegram/SourceFiles/info/info.style | 2 +- .../info/profile/info_profile_actions.cpp | 44 ++- .../info/profile/info_profile_badge.cpp | 10 +- .../info/profile/info_profile_badge.h | 4 +- .../info/profile/info_profile_cover.cpp | 26 +- Telegram/SourceFiles/ui/unread_badge.cpp | 22 +- Telegram/SourceFiles/ui/unread_badge.h | 6 +- 16 files changed, 565 insertions(+), 41 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/peers/verify_peers_box.cpp create mode 100644 Telegram/SourceFiles/boxes/peers/verify_peers_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 2e5178a2e3..3ad4fa5a84 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -246,6 +246,8 @@ PRIVATE boxes/peers/prepare_short_info_box.h boxes/peers/replace_boost_box.cpp boxes/peers/replace_boost_box.h + boxes/peers/verify_peers_box.cpp + boxes/peers/verify_peers_box.h boxes/about_box.cpp boxes/about_box.h boxes/about_sponsored_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f1d8dbba54..64077c69ab 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1628,11 +1628,50 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_peer_bot_star_ref" = "Affiliate Program"; "lng_manage_peer_bot_star_ref_off" = "Off"; "lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there."; +"lng_manage_peer_bot_verify" = "Verify Accounts"; "lng_manage_peer_bot_edit_intro" = "Edit Intro"; "lng_manage_peer_bot_edit_commands" = "Edit Commands"; "lng_manage_peer_bot_edit_settings" = "Change Bot Settings"; "lng_manage_peer_bot_about" = "Use {bot} to manage this bot."; +"lng_bot_verify_title" = "Choose Chat to Verify"; +"lng_bot_verify_bot_title" = "Verify Bot"; +"lng_bot_verify_bot_text" = "Do you want to verify {name} with your verification mark and description?"; +"lng_bot_verify_bot_about" = "You can customize your description for each bot."; +"lng_bot_verify_bot_submit" = "Verify Bot"; +"lng_bot_verify_bot_sent" = "{name} has been notified and will receive your verification mark and description upon accepting."; +"lng_bot_verify_bot_remove" = "This bot is already verified by you. Do you want to remove verification?"; +"lng_bot_verify_bot_telegram" = "This bot is verified as official by the representatives of Telegram."; +"lng_bot_verify_bot_company" = "This bot was verified by {company}."; +"lng_bot_verify_user_title" = "Verify User"; +"lng_bot_verify_user_text" = "Do you want to verify {name} with your verification mark and description?"; +"lng_bot_verify_user_about" = "You can customize your description for each account."; +"lng_bot_verify_user_submit" = "Verify User"; +"lng_bot_verify_user_sent" = "{name} has been notified and will receive your verification mark and description upon accepting."; +"lng_bot_verify_user_remove" = "This account is already verified by you. Do you want to remove verification?"; +"lng_bot_verify_user_telegram" = "This account is verified as official by the representatives of Telegram."; +"lng_bot_verify_user_company" = "This account was verified by {company}."; +"lng_bot_verify_channel_title" = "Verify Channel"; +"lng_bot_verify_channel_text" = "Do you want to verify {name} with your verification mark and description?"; +"lng_bot_verify_channel_about" = "You can customize your description for each channel."; +"lng_bot_verify_channel_submit" = "Verify Channel"; +"lng_bot_verify_channel_sent" = "{name} has been notified and will receive your verification mark and description upon accepting."; +"lng_bot_verify_channel_remove" = "This channel is already verified by you. Do you want to remove verification?"; +"lng_bot_verify_channel_telegram" = "This channel is verified as official by the representatives of Telegram."; +"lng_bot_verify_channel_company" = "This channel was verified by {company}."; +"lng_bot_verify_group_title" = "Verify Group"; +"lng_bot_verify_group_text" = "Do you want to verify {name} with your verification mark and description?"; +"lng_bot_verify_group_about" = "You can customize your description for each group."; +"lng_bot_verify_group_submit" = "Verify Group"; +"lng_bot_verify_group_sent" = "{name} has been notified and will receive your verification mark and description upon accepting."; +"lng_bot_verify_group_remove" = "This group is already verified by you. Do you want to remove verification?"; +"lng_bot_verify_group_telegram" = "This community is verified as official by the representatives of Telegram."; +"lng_bot_verify_group_company" = "This community was verified by {company}."; +"lng_bot_verify_description_label" = "Description"; +"lng_bot_verify_remove_title" = "Remove verification"; +"lng_bot_verify_remove_submit" = "Remove"; +"lng_bot_verify_remove_done" = "You've removed this verification."; + "lng_star_ref_title" = "Affiliate Program"; "lng_star_ref_about" = "Reward those who help grow your user base."; "lng_star_ref_share_title" = "Share revenue with affiliates"; @@ -3484,8 +3523,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_verification_codes" = "Verification Codes"; "lng_verification_codes_about" = "Third-party services, like websites and stores, can send verification codes to your phone number via Telegram instead of SMS. Such codes will appear in this chat.\n\nIf you didn't request any codes — don't worry! Most likely, someone made a mistake when entering their number."; -"lng_verified_by_telegram" = "This community is verified as official by the representatives of Telegram."; - "lng_archived_name" = "Archived chats"; "lng_archived_add" = "Archive"; "lng_archived_remove" = "Unarchive"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index f80f4e73d5..7fea20e340 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_requests_box.h" #include "boxes/peers/edit_peer_reactions.h" #include "boxes/peers/replace_boost_box.h" +#include "boxes/peers/verify_peers_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/stickers_box.h" #include "boxes/username_box.h" @@ -366,6 +367,7 @@ private: void fillBotEditIntroButton(); void fillBotEditCommandsButton(); void fillBotEditSettingsButton(); + void fillBotVerifyAccounts(); void submitTitle(); void submitDescription(); @@ -1206,6 +1208,7 @@ void Controller::fillManageSection() { Ui::Text::RichLangValue), st::boxDividerLabel), st::defaultBoxDividerLabelPadding)); + fillBotVerifyAccounts(); return; } @@ -1796,6 +1799,39 @@ void Controller::fillBotEditSettingsButton() { { &st::menuIconSettings }); } +void Controller::fillBotVerifyAccounts() { + Expects(_isBot); + + const auto user = _peer->asUser(); + const auto wrap = _controls.buttonsLayout->add( + object_ptr>( + _controls.buttonsLayout, + object_ptr( + _controls.buttonsLayout))); + wrap->toggleOn(rpl::single( + rpl::empty + ) | rpl::then(user->owner().botCommandsChanges( + ) | rpl::filter( + rpl::mappers::_1 == _peer + ) | rpl::to_empty) | rpl::map([=] { + const auto info = user->botInfo.get(); + return info && info->verifierSettings; + })); + + const auto inner = wrap->entity(); + Ui::AddSkip(inner); + AddButtonWithCount( + inner, + tr::lng_manage_peer_bot_verify(), + rpl::never(), + [controller = _navigation->parentController(), user] { + controller->show(MakeVerifyPeersBox(controller, user)); + }, + { &st::menuIconFactcheck }); + Ui::AddSkip(inner); + Ui::AddDivider(inner); +} + void Controller::submitTitle() { Expects(_controls.title != nullptr); diff --git a/Telegram/SourceFiles/boxes/peers/verify_peers_box.cpp b/Telegram/SourceFiles/boxes/peers/verify_peers_box.cpp new file mode 100644 index 0000000000..cafc5d3b0f --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/verify_peers_box.cpp @@ -0,0 +1,300 @@ +/* +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 "boxes/peers/verify_peers_box.h" + +#include "apiwrap.h" +#include "boxes/peer_list_controllers.h" +#include "data/data_user.h" +#include "history/history.h" +#include "main/main_app_config.h" +#include "main/main_session.h" +#include "ui/boxes/confirm_box.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/vertical_list.h" +#include "window/window_session_controller.h" +#include "styles/style_boxes.h" +#include "styles/style_layers.h" + +namespace { + +constexpr auto kSetupVerificationToastDuration = 4 * crl::time(1000); + +class Controller final : public ChatsListBoxController { +public: + Controller(not_null session, not_null bot) + : ChatsListBoxController(session) + , _bot(bot) { + } + + Main::Session &session() const override; + + void rowClicked(gsl::not_null row) override; + +private: + std::unique_ptr createRow(not_null history) override; + void prepareViewHook() override; + + void confirmAdd(not_null peer); + void confirmRemove(not_null peer); + + const not_null _bot; + +}; + +void Setup( + not_null bot, + not_null peer, + QString description, + Fn done) { + using Flag = MTPbots_SetCustomVerification::Flag; + bot->session().api().request(MTPbots_SetCustomVerification( + MTP_flags(Flag::f_bot + | Flag::f_enabled + | (description.isEmpty() ? Flag() : Flag::f_custom_description)), + bot->inputUser, + peer->input, + MTP_string(description) + )).done([=] { + done(QString()); + }).fail([=](const MTP::Error &error) { + done(error.type()); + }).send(); +} + +void Remove( + not_null bot, + not_null peer, + Fn done) { + bot->session().api().request(MTPbots_SetCustomVerification( + MTP_flags(MTPbots_SetCustomVerification::Flag::f_bot), + bot->inputUser, + peer->input, + MTPstring() + )).done([=] { + done(QString()); + }).fail([=](const MTP::Error &error) { + done(error.type()); + }).send(); +} + +Main::Session &Controller::session() const { + return _bot->session(); +} + +void Controller::rowClicked(gsl::not_null row) { + const auto peer = row->peer(); + const auto details = peer->verifyDetails(); + const auto already = details && (details->botId == peerToUser(_bot->id)); + if (already) { + confirmRemove(peer); + } else { + confirmAdd(peer); + } +} + +void Controller::confirmAdd(not_null peer) { + const auto bot = _bot; + const auto show = delegate()->peerListUiShow(); + show->show(Box([=](not_null box) { + struct State { + Ui::InputField *field = nullptr; + QString description; + bool sent = false; + }; + const auto settings = bot->botInfo + ? bot->botInfo->verifierSettings.get() + : nullptr; + const auto state = std::make_shared(State{ + .description = settings ? settings->customDescription : QString() + }); + + const auto limit = session().appConfig().get( + u"bot_verification_description_length_limit"_q, + 70); + const auto send = [=] { + if (state->description.size() > limit) { + state->field->showError(); + return; + } else if (state->sent) { + return; + } + state->sent = true; + const auto weak = Ui::MakeWeak(box); + Setup(bot, peer, state->description, [=](QString error) { + if (error.isEmpty()) { + if (const auto strong = weak.data()) { + strong->closeBox(); + } + show->showToast({ + .text = PeerVerifyPhrases(peer).sent( + tr::now, + lt_name, + Ui::Text::Bold(peer->shortName()), + Ui::Text::WithEntities), + .duration = kSetupVerificationToastDuration, + }); + } else { + state->sent = false; + show->showToast(error); + } + }); + }; + + const auto phrases = PeerVerifyPhrases(peer); + Ui::ConfirmBox(box, { + .text = phrases.text( + lt_name, + rpl::single(Ui::Text::Bold(peer->shortName())), + Ui::Text::WithEntities), + .confirmed = send, + .confirmText = phrases.submit(), + .title = phrases.title(), + }); + + Ui::AddSubsectionTitle( + box->verticalLayout(), + tr::lng_bot_verify_description_label(), + QMargins(0, 0, 0, -st::defaultSubsectionTitlePadding.bottom())); + + const auto field = box->addRow(object_ptr( + box, + st::createPollField, + Ui::InputField::Mode::NoNewlines, + (settings + ? phrases.company(lt_company, rpl::single(settings->company)) + : rpl::single(QString())), + state->description + ), st::createPollFieldPadding); + state->field = field; + + box->setFocusCallback([=] { + field->setFocusFast(); + }); + + Ui::AddSkip(box->verticalLayout()); + + field->changes() | rpl::start_with_next([=] { + state->description = field->getLastText(); + }, field->lifetime()); + + field->setMaxLength(limit * 2); + Ui::AddLengthLimitLabel(field, limit, std::nullopt); + + Ui::AddDividerText(box->verticalLayout(), phrases.about()); + })); +} + +void Controller::confirmRemove(not_null peer) { + const auto bot = _bot; + const auto show = delegate()->peerListUiShow(); + show->show(Box([=](not_null box) { + const auto sent = std::make_shared(); + const auto send = [=] { + if (*sent) { + return; + } + *sent = true; + const auto weak = Ui::MakeWeak(box); + Remove(bot, peer, [=](QString error) { + if (error.isEmpty()) { + if (const auto strong = weak.data()) { + strong->closeBox(); + } + show->showToast(tr::lng_bot_verify_remove_done(tr::now)); + } else { + *sent = false; + show->showToast(error); + } + }); + }; + Ui::ConfirmBox(box, { + .text = PeerVerifyPhrases(peer).remove(), + .confirmed = send, + .confirmText = tr::lng_bot_verify_remove_submit(), + .confirmStyle = &st::attentionBoxButton, + .title = tr::lng_bot_verify_remove_title(), + }); + })); +} + +auto Controller::createRow(not_null history) +-> std::unique_ptr { + const auto peer = history->peer; + const auto may = peer->isUser() || peer->isChannel(); + return may ? std::make_unique(history) : nullptr; +} + +void Controller::prepareViewHook() { +} + +} // namespace + +object_ptr MakeVerifyPeersBox( + not_null window, + not_null bot) { + const auto session = &window->session(); + auto controller = std::make_unique(session, bot); + auto init = [=](not_null box) { + box->setTitle(tr::lng_bot_verify_title()); + box->addButton(tr::lng_box_done(), [=] { + box->closeBox(); + }); + }; + return Box(std::move(controller), std::move(init)); +} + +VerifyPhrases PeerVerifyPhrases(not_null peer) { + if (const auto user = peer->asUser()) { + if (user->isBot()) { + return { + .title = tr::lng_bot_verify_bot_title, + .text = tr::lng_bot_verify_bot_text, + .about = tr::lng_bot_verify_bot_about, + .submit = tr::lng_bot_verify_bot_submit, + .sent = tr::lng_bot_verify_bot_sent, + .remove = tr::lng_bot_verify_bot_remove, + .telegram = tr::lng_bot_verify_bot_telegram, + .company = tr::lng_bot_verify_bot_company, + }; + } else { + return { + .title = tr::lng_bot_verify_user_title, + .text = tr::lng_bot_verify_user_text, + .about = tr::lng_bot_verify_user_about, + .submit = tr::lng_bot_verify_user_submit, + .sent = tr::lng_bot_verify_user_sent, + .remove = tr::lng_bot_verify_user_remove, + .telegram = tr::lng_bot_verify_user_telegram, + .company = tr::lng_bot_verify_user_company, + }; + } + } else if (peer->isBroadcast()) { + return { + .title = tr::lng_bot_verify_channel_title, + .text = tr::lng_bot_verify_channel_text, + .about = tr::lng_bot_verify_channel_about, + .submit = tr::lng_bot_verify_channel_submit, + .sent = tr::lng_bot_verify_channel_sent, + .remove = tr::lng_bot_verify_channel_remove, + .telegram = tr::lng_bot_verify_channel_telegram, + .company = tr::lng_bot_verify_channel_company, + }; + } + return { + .title = tr::lng_bot_verify_group_title, + .text = tr::lng_bot_verify_group_text, + .about = tr::lng_bot_verify_group_about, + .submit = tr::lng_bot_verify_group_submit, + .sent = tr::lng_bot_verify_group_sent, + .remove = tr::lng_bot_verify_group_remove, + .telegram = tr::lng_bot_verify_group_telegram, + .company = tr::lng_bot_verify_group_company, + }; +} diff --git a/Telegram/SourceFiles/boxes/peers/verify_peers_box.h b/Telegram/SourceFiles/boxes/peers/verify_peers_box.h new file mode 100644 index 0000000000..5e2a1b3fb7 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/verify_peers_box.h @@ -0,0 +1,38 @@ +/* +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 +*/ +#pragma once + +#include "base/object_ptr.h" +#include "lang/lang_keys.h" + +class PeerData; +class UserData; + +namespace Ui { +class BoxContent; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +[[nodiscard]] object_ptr MakeVerifyPeersBox( + not_null window, + not_null bot); + +struct VerifyPhrases { + tr::phrase<> title; + tr::phrase text; + tr::phrase<> about; + tr::phrase<> submit; + tr::phrase sent; + tr::phrase<> remove; + tr::phrase<> telegram; + tr::phrase company; +}; +[[nodiscard]] VerifyPhrases PeerVerifyPhrases(not_null peer); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index e8173de68f..42735d270a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -142,7 +142,7 @@ void CheckForSwitchInlineButton(not_null item) { const auto flags = TextParseLinks; return { .botId = UserId(data.vbot_id().v), - .iconBgId = SerializeCustomEmojiId(DocumentId(data.vicon().v)), + .iconBgId = DocumentId(data.vicon().v), .company = qs(data.vcompany()), .description = TextUtilities::ParseEntities(description, flags), }; @@ -353,7 +353,7 @@ Session::Session(not_null session) Ui::VerifyDetails Session::verifiedByTelegram() { - if (_verifiedByTelegramIconBgId.isEmpty()) { + if (!_verifiedByTelegramIconBgId) { const auto bg = ChatHelpers::GenerateLocalSticker( _session, u":/gui/art/verified_bg.webp"_q); @@ -362,14 +362,13 @@ Ui::VerifyDetails Session::verifiedByTelegram() { _session, u":/gui/art/verified_fg.webp"_q); fg->overrideEmojiUsesTextColor(true); - _verifiedByTelegramIconBgId = Data::SerializeCustomEmojiId(bg); - _verifiedByTelegramIconFgId = Data::SerializeCustomEmojiId(fg); + _verifiedByTelegramIconBgId = bg->id; + _verifiedByTelegramIconFgId = fg->id; } return { .iconBgId = _verifiedByTelegramIconBgId, .iconFgId = _verifiedByTelegramIconFgId, .company = u"Telegram"_q, - .description = { tr::lng_verified_by_telegram(tr::now) }, }; } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index f24190c40c..f0aaed6a41 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -1142,8 +1142,8 @@ private: const std::unique_ptr _businessInfo; std::unique_ptr _shortcutMessages; - QString _verifiedByTelegramIconBgId; - QString _verifiedByTelegramIconFgId; + DocumentId _verifiedByTelegramIconBgId = 0; + DocumentId _verifiedByTelegramIconFgId = 0; MsgId _nonHistoryEntryId = ShortcutMaxMsgId; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 01b51ec46d..ba0cc0f1be 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -36,6 +36,30 @@ constexpr auto kSetOnlineAfterActivity = TimeId(30); using UpdateFlag = Data::PeerUpdate::Flag; +bool ApplyBotVerifierSettings( + not_null info, + const MTPBotVerifierSettings *settings) { + if (!settings) { + const auto taken = base::take(info->verifierSettings); + return taken != nullptr; + } + const auto &data = settings->data(); + const auto parsed = BotVerifierSettings{ + .iconId = DocumentId(data.vicon().v), + .company = qs(data.vcompany()), + .customDescription = qs(data.vcustom_description().value_or_empty()), + }; + if (!info->verifierSettings) { + info->verifierSettings = std::make_unique( + parsed); + return true; + } else if (*info->verifierSettings != parsed) { + *info->verifierSettings = parsed; + return true; + } + return false; +} + } // namespace BotInfo::BotInfo() = default; @@ -232,7 +256,11 @@ void UserData::setPersonalChannel(ChannelId channelId, MsgId messageId) { } } -void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) { +void UserData::setName( + const QString &newFirstName, + const QString &newLastName, + const QString &newPhoneName, + const QString &newUsername) { bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty(); QString newFullName; @@ -245,7 +273,14 @@ void UserData::setName(const QString &newFirstName, const QString &newLastName, firstName = newFirstName; lastName = newLastName; } - newFullName = lastName.isEmpty() ? firstName : tr::lng_full_name(tr::now, lt_first_name, firstName, lt_last_name, lastName); + newFullName = lastName.isEmpty() + ? firstName + : tr::lng_full_name( + tr::now, + lt_first_name, + firstName, + lt_last_name, + lastName); } updateNameDelayed(newFullName, newPhoneName, newUsername); } @@ -372,8 +407,14 @@ void UserData::setBotInfo(const MTPBotInfo &info) { = botInfo->botAppColorBodyNight = QColor(0, 0, 0, 0); } + const auto changedVerifierSettings = ApplyBotVerifierSettings( + botInfo.get(), + d.vverifier_settings()); - if (changedCommands || changedButton || privacyChanged) { + if (changedCommands + || changedButton + || privacyChanged + || changedVerifierSettings) { owner().botCommandsChanged(this); } } break; diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index b599f1ed3f..5be9069765 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -31,6 +31,20 @@ struct StarRefProgram { StarRefProgram) = default; }; +struct BotVerifierSettings { + DocumentId iconId = 0; + QString company; + QString customDescription; + + explicit operator bool() const { + return iconId != 0; + } + + friend inline bool operator==( + const BotVerifierSettings &a, + const BotVerifierSettings &b) = default; +}; + struct BotInfo { BotInfo(); @@ -57,6 +71,7 @@ struct BotInfo { ChatAdminRights channelAdminRights; StarRefProgram starRefProgram; + std::unique_ptr verifierSettings; int version = 0; int descriptionVersion = 0; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 1ae86e9d38..ffcd02f46d 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -508,7 +508,7 @@ infoOpenApp: RoundButton(defaultActiveButton) { } infoOpenAppMargin: margins(16px, 12px, 16px, 12px); -infoPersonalChannelIconPosition: point(25px, 20px); +infoPersonalChannelIconPosition: point(25px, 12px); infoPersonalChannelNameLabel: FlatLabel(infoProfileStatus) { textFg: windowBoldFg; style: semiboldTextStyle; diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index a6716aa913..c6085492c8 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_contact_box.h" #include "boxes/peers/edit_participants_box.h" #include "boxes/peers/edit_peer_info_box.h" +#include "boxes/peers/verify_peers_box.h" #include "boxes/report_messages_box.h" #include "boxes/share_box.h" #include "boxes/star_gift_box.h" @@ -960,6 +961,7 @@ private: object_ptr setupPersonalChannel(not_null user); object_ptr setupInfo(); object_ptr setupMuteToggle(); + void setupAboutVerification(); void setupMainApp(); void setupBotPermissions(); void setupMainButtons(); @@ -1560,8 +1562,6 @@ object_ptr DetailsFiller::setupPersonalChannel( })); onlyChannelWrap->finishAnimating(); - Ui::AddDivider(onlyChannelWrap->entity()); - auto text = rpl::duplicate( channel ) | rpl::map([=](ChannelData *channel) { @@ -1591,6 +1591,8 @@ object_ptr DetailsFiller::setupPersonalChannel( onlyChannelWrap, st::infoIconMediaChannel, st::infoPersonalChannelIconPosition); + + Ui::AddDivider(onlyChannelWrap->entity()); } { @@ -1620,7 +1622,7 @@ object_ptr DetailsFiller::setupPersonalChannel( messageChannelWrap->toggle(false, anim::type::instant); clear(); - Ui::AddDivider(messageChannelWrap->entity()); + Ui::AddSkip(messageChannelWrap->entity()); const auto inner = messageChannelWrap->entity()->add( @@ -1751,6 +1753,7 @@ object_ptr DetailsFiller::setupPersonalChannel( } inner->setAttribute(Qt::WA_TransparentForMouseEvents); Ui::AddSkip(messageChannelWrap->entity()); + Ui::AddDivider(messageChannelWrap->entity()); Ui::ToggleChildrenVisibility(messageChannelWrap->entity(), true); Ui::ToggleChildrenVisibility(line, true); @@ -1835,6 +1838,35 @@ object_ptr DetailsFiller::setupMuteToggle() { return result; } +void DetailsFiller::setupAboutVerification() { + const auto peer = _peer; + const auto inner = _wrap->add(object_ptr(_wrap)); + peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::VerifyInfo + ) | rpl::start_with_next([=] { + const auto info = peer->verifyDetails(); + while (inner->count()) { + delete inner->widgetAt(0); + } + if (!info) { + Ui::AddDivider(inner); + } else if (!info->description.empty()) { + Ui::AddDividerText(inner, rpl::single(info->description)); + } else { + const auto phrases = PeerVerifyPhrases(peer); + Ui::AddDividerText( + inner, + (_peer->verifiedByTelegram() + ? phrases.telegram() + : phrases.company( + lt_company, + rpl::single(info->company)))); + } + inner->resizeToWidth(inner->width()); + }, inner->lifetime()); +} + void DetailsFiller::setupMainApp() { const auto button = _wrap->add( object_ptr( @@ -2103,10 +2135,14 @@ Ui::MultiSlideTracker DetailsFiller::fillDiscussionButtons( object_ptr DetailsFiller::fill() { Expects(!_topic || !_topic->creating()); + if (!_topic) { + setupAboutVerification(); + } else { + add(object_ptr(_wrap)); + } if (const auto user = _peer->asUser()) { add(setupPersonalChannel(user)); } - add(object_ptr(_wrap)); add(CreateSkipWidget(_wrap)); add(setupInfo()); if (const auto user = _peer->asUser()) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp index ee2c1a0352..b8c74f4c1f 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp @@ -37,9 +37,7 @@ namespace { } return Badge::Content{ badge, - (emojiStatusId - ? Data::SerializeCustomEmojiId(emojiStatusId) - : QString()), + emojiStatusId ? emojiStatusId : DocumentId(), }; }); } @@ -120,14 +118,14 @@ void Badge::setContent(Content content) { case BadgeType::Premium: { const auto id = _content.emojiStatusId; const auto innerId = _content.emojiStatusInnerId; - if (!id.isEmpty() || !innerId.isEmpty()) { - _emojiStatus = !id.isEmpty() + if (id || innerId) { + _emojiStatus = id ? _session->data().customEmojiManager().create( id, [raw = _view.data()] { raw->update(); }, sizeTag()) : nullptr; - _statusInner = !innerId.isEmpty() + _statusInner = innerId ? _session->data().customEmojiManager().create( innerId, [raw = _view.data()] { raw->update(); }, diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.h b/Telegram/SourceFiles/info/profile/info_profile_badge.h index ea24f2fb6e..efff823cb8 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_badge.h +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.h @@ -58,8 +58,8 @@ public: struct Content { BadgeType badge = BadgeType::None; - QString emojiStatusId; - QString emojiStatusInnerId; + DocumentId emojiStatusId = 0; + DocumentId emojiStatusInnerId = 0; friend inline constexpr bool operator==(Content, Content) = default; }; diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 0b69500b4e..3934cbb7b3 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -94,6 +94,16 @@ auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) { : st::infoProfileCover; } +[[nodiscard]] QMargins LargeCustomEmojiMargins() { + const auto ratio = style::DevicePixelRatio(); + const auto emoji = Ui::Emoji::GetSizeLarge() / ratio; + const auto size = Data::FrameSizeFromTag(Data::CustomEmojiSizeTag::Large) + / ratio; + const auto left = (size - emoji) / 2; + const auto right = size - emoji - left; + return { left, left, right, right }; +} + } // namespace TopicIconView::TopicIconView( @@ -302,8 +312,8 @@ Cover::Cover( const auto details = peer->verifyDetails(); return Badge::Content{ .badge = details ? BadgeType::Verified : BadgeType::None, - .emojiStatusId = details ? details->iconBgId : QString(), - .emojiStatusInnerId = details ? details->iconFgId : QString(), + .emojiStatusId = details ? details->iconBgId : DocumentId(), + .emojiStatusInnerId = details ? details->iconFgId : DocumentId(), }; }); } @@ -731,10 +741,16 @@ void Cover::refreshNameGeometry(int newWidth) { auto nameLeft = _st.nameLeft; const auto badgeTop = _st.nameTop; const auto badgeBottom = _st.nameTop + _name->height(); - _verify->move(nameLeft, badgeTop, badgeBottom); + const auto margins = LargeCustomEmojiMargins(); + + _verify->move(nameLeft - margins.left(), badgeTop, badgeBottom); if (const auto widget = _verify->widget()) { - nameLeft += widget->width() + st::infoVerifiedCheckPosition.x(); - nameWidth -= widget->width() + st::infoVerifiedCheckPosition.x(); + const auto skip = widget->width() + + st::infoVerifiedCheckPosition.x() + - margins.left() + - margins.right(); + nameLeft += skip; + nameWidth -= skip; } _name->resizeToNaturalWidth(nameWidth); _name->moveToLeft(nameLeft, _st.nameTop, newWidth); diff --git a/Telegram/SourceFiles/ui/unread_badge.cpp b/Telegram/SourceFiles/ui/unread_badge.cpp index dee9af528f..30956a01e4 100644 --- a/Telegram/SourceFiles/ui/unread_badge.cpp +++ b/Telegram/SourceFiles/ui/unread_badge.cpp @@ -225,16 +225,18 @@ bool PeerBadge::ready(const VerifyDetails *details) const { } else if (!_verifiedData) { return false; } - if (details->iconBgId.isEmpty()) { + if (!details->iconBgId) { _verifiedData->bg = nullptr; } else if (!_verifiedData->bg - || _verifiedData->bg->entityData() != details->iconBgId) { + || (_verifiedData->bg->entityData() + != Data::SerializeCustomEmojiId(details->iconBgId))) { return false; } - if (details->iconFgId.isEmpty()) { + if (!details->iconFgId) { _verifiedData->fg = nullptr; } else if (!_verifiedData->fg - || _verifiedData->fg->entityData() != details->iconFgId) { + || (_verifiedData->fg->entityData() + != Data::SerializeCustomEmojiId(details->iconFgId))) { return false; } return true; @@ -247,11 +249,15 @@ void PeerBadge::set( if (!_verifiedData) { _verifiedData = std::make_unique(); } - if (!details->iconBgId.isEmpty()) { - _verifiedData->bg = factory(details->iconBgId, repaint); + if (details->iconBgId) { + _verifiedData->bg = factory( + Data::SerializeCustomEmojiId(details->iconBgId), + repaint); } - if (!details->iconFgId.isEmpty()) { - _verifiedData->fg = factory(details->iconFgId, repaint); + if (details->iconFgId) { + _verifiedData->fg = factory( + Data::SerializeCustomEmojiId(details->iconFgId), + repaint); } } diff --git a/Telegram/SourceFiles/ui/unread_badge.h b/Telegram/SourceFiles/ui/unread_badge.h index 2e68869ada..cc8b4ce13e 100644 --- a/Telegram/SourceFiles/ui/unread_badge.h +++ b/Telegram/SourceFiles/ui/unread_badge.h @@ -34,13 +34,13 @@ private: struct VerifyDetails { UserId botId = 0; - QString iconBgId; - QString iconFgId; + DocumentId iconBgId = 0; + DocumentId iconFgId = 0; QString company; TextWithEntities description; explicit operator bool() const { - return !iconBgId.isEmpty() || !iconFgId.isEmpty(); + return iconBgId || iconFgId; } friend inline bool operator==( const VerifyDetails &,