diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index 18cab335e..77b9872bf 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -570,9 +570,7 @@ ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( [[nodiscard]] Info::Profile::BadgeType ConfirmInviteBox::BadgeForInvite( const ChatInvite &invite) { using Type = Info::Profile::BadgeType; - return invite.isVerified - ? Type::Verified - : invite.isScam + return invite.isScam ? Type::Scam : invite.isFake ? Type::Fake diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 66c0fb9b3..cff431720 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -74,46 +74,47 @@ struct PeerUpdate { Color = (1ULL << 14), BackgroundEmoji = (1ULL << 15), StoriesState = (1ULL << 16), + VerifyInfo = (1ULL << 17), // For users - CanShareContact = (1ULL << 17), - IsContact = (1ULL << 18), - PhoneNumber = (1ULL << 19), - OnlineStatus = (1ULL << 20), - BotCommands = (1ULL << 21), - BotCanBeInvited = (1ULL << 22), - BotStartToken = (1ULL << 23), - CommonChats = (1ULL << 24), - PeerGifts = (1ULL << 25), - HasCalls = (1ULL << 26), - SupportInfo = (1ULL << 27), - IsBot = (1ULL << 28), - EmojiStatus = (1ULL << 29), - BusinessDetails = (1ULL << 30), - Birthday = (1ULL << 31), - PersonalChannel = (1ULL << 32), - StarRefProgram = (1ULL << 33), + CanShareContact = (1ULL << 18), + IsContact = (1ULL << 19), + PhoneNumber = (1ULL << 20), + OnlineStatus = (1ULL << 21), + BotCommands = (1ULL << 22), + BotCanBeInvited = (1ULL << 23), + BotStartToken = (1ULL << 24), + CommonChats = (1ULL << 25), + PeerGifts = (1ULL << 26), + HasCalls = (1ULL << 27), + SupportInfo = (1ULL << 28), + IsBot = (1ULL << 29), + EmojiStatus = (1ULL << 30), + BusinessDetails = (1ULL << 31), + Birthday = (1ULL << 32), + PersonalChannel = (1ULL << 33), + StarRefProgram = (1ULL << 34), // For chats and channels - InviteLinks = (1ULL << 34), - Members = (1ULL << 35), - Admins = (1ULL << 36), - BannedUsers = (1ULL << 37), - Rights = (1ULL << 38), - PendingRequests = (1ULL << 39), - Reactions = (1ULL << 40), + InviteLinks = (1ULL << 35), + Members = (1ULL << 36), + Admins = (1ULL << 37), + BannedUsers = (1ULL << 38), + Rights = (1ULL << 39), + PendingRequests = (1ULL << 40), + Reactions = (1ULL << 41), // For channels - ChannelAmIn = (1ULL << 41), - StickersSet = (1ULL << 42), - EmojiSet = (1ULL << 43), - ChannelLinkedChat = (1ULL << 44), - ChannelLocation = (1ULL << 45), - Slowmode = (1ULL << 46), - GroupCall = (1ULL << 47), + ChannelAmIn = (1ULL << 42), + StickersSet = (1ULL << 43), + EmojiSet = (1ULL << 44), + ChannelLinkedChat = (1ULL << 45), + ChannelLocation = (1ULL << 46), + Slowmode = (1ULL << 47), + GroupCall = (1ULL << 48), // For iteration - LastUsedBit = (1ULL << 47), + LastUsedBit = (1ULL << 48), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 157a16b39..8d3d3797a 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_invite.h" #include "api/api_invite_links.h" #include "apiwrap.h" +#include "ui/unread_badge.h" #include "window/notifications_manager.h" namespace { @@ -713,6 +714,24 @@ bool ChannelData::canRestrictParticipant( return adminRights() & AdminRight::BanUsers; } +void ChannelData::setVerifyDetails(Ui::VerifyDetails details) { + if (_verifyDetails && !verifiedByTelegram() && !details) { + return; AssertIsDebug(); + } + if (!details) { + if (_verifyDetails) { + _verifyDetails = nullptr; + session().changes().peerUpdated(this, UpdateFlag::VerifyInfo); + } + } else if (!_verifyDetails) { + _verifyDetails = std::make_unique(details); + session().changes().peerUpdated(this, UpdateFlag::VerifyInfo); + } else if (*_verifyDetails != details) { + *_verifyDetails = details; + session().changes().peerUpdated(this, UpdateFlag::VerifyInfo); + } +} + void ChannelData::setAdminRights(ChatAdminRights rights) { if (rights == adminRights()) { return; diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 90f3b9274..8aa67baee 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -376,6 +376,11 @@ public: [[nodiscard]] bool canRestrictParticipant( not_null participant) const; + void setVerifyDetails(Ui::VerifyDetails details); + [[nodiscard]] Ui::VerifyDetails *verifyDetails() const { + return _verifyDetails.get(); + } + void setInviteLink(const QString &newInviteLink); [[nodiscard]] QString inviteLink() const { return _inviteLink; @@ -546,6 +551,8 @@ private: std::unique_ptr _call; PeerId _callDefaultJoinAs = 0; + std::unique_ptr _verifyDetails; + }; namespace Data { diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 3b3fdbedb..00827d6ef 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/empty_userpic.h" #include "ui/text/text_options.h" #include "ui/painter.h" +#include "ui/unread_badge.h" #include "ui/ui_utility.h" #include "history/history.h" #include "history/view/history_view_element.h" @@ -1246,6 +1247,22 @@ void PeerData::setStoriesHidden(bool hidden) { } } +Ui::VerifyDetails *PeerData::verifyDetails() const { + if (const auto user = asUser()) { + return user->verifyDetails(); + } else if (const auto channel = asChannel()) { + return channel->verifyDetails(); + } + return nullptr; +} + +bool PeerData::verifiedByTelegram() const { + if (const auto details = verifyDetails()) { + return (details->iconBgId == owner().verifiedByTelegram().iconBgId); + } + return false; +} + Data::Forum *PeerData::forum() const { if (const auto channel = asChannel()) { return channel->forum(); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 354bac4a5..7eff528f0 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -23,6 +23,7 @@ enum class ChatRestriction; namespace Ui { class EmptyUserpic; +struct VerifyDetails; } // namespace Ui namespace Main { @@ -233,6 +234,9 @@ public: [[nodiscard]] bool hasStoriesHidden() const; void setStoriesHidden(bool hidden); + [[nodiscard]] Ui::VerifyDetails *verifyDetails() const; + [[nodiscard]] bool verifiedByTelegram() const; + [[nodiscard]] bool isNotificationsUser() const { return (id == peerFromUser(333000)) || (id == kServiceNotificationsId); @@ -521,7 +525,7 @@ private: base::flat_set _nameFirstLetters; DocumentId _emojiStatusId = 0; - uint64 _backgroundEmojiId = 0; + DocumentId _backgroundEmojiId = 0; crl::time _lastFullUpdate = 0; QString _name; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 76f84d801..fc3646e27 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" #include "history/view/history_view_element.h" #include "inline_bots/inline_bot_layout_item.h" +#include "storage/localimageloader.h" #include "storage/storage_account.h" #include "storage/storage_encrypted_file.h" #include "media/player/media_player_instance.h" // instance()->play() @@ -79,6 +80,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/random.h" #include "spellcheck/spellcheck_highlight_syntax.h" +#include "ui/chat/attach/attach_prepare.h" +#include "ui/unread_badge.h" +#include "styles/style_dialogs.h" +#include + namespace Data { namespace { @@ -329,6 +335,25 @@ Session::Session(not_null session) }, _lifetime); } + +Ui::VerifyDetails Session::verifiedByTelegram() { + if (_verifiedByTelegramIconBgId.isEmpty()) { + auto &manager = customEmojiManager(); + _verifiedByTelegramIconBgId = manager.registerInternalEmoji( + st::dialogsVerifiedBg); + _verifiedByTelegramIconFgId = manager.registerInternalEmoji( + st::dialogsVerifiedFg); + } + return { + .iconBgId = _verifiedByTelegramIconBgId, + .iconFgId = _verifiedByTelegramIconFgId, + .description = { + u"This community is verified as official " + "by the representatives of Telegram."_q, + }, + }; +} + void Session::subscribeForTopicRepliesLists() { repliesReadTillUpdates( ) | rpl::start_with_next([=](const RepliesReadTillUpdate &update) { @@ -569,6 +594,11 @@ not_null Session::processUser(const MTPUser &data) { | (data.is_stories_hidden() ? Flag::StoriesHidden : Flag()) : Flag()); result->setFlags((result->flags() & ~flagsMask) | flagsSet); + if (data.is_verified()) { + result->setVerifyDetails(verifiedByTelegram()); + } else { + result->setVerifyDetails({}); + } if (minimal) { if (result->input.type() == mtpc_inputPeerEmpty) { result->input = MTP_inputPeerUser( @@ -984,6 +1014,11 @@ not_null Session::processChat(const MTPChat &data) { ? Flag::StoriesHidden : Flag()); channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); + if (data.is_verified()) { + channel->setVerifyDetails(verifiedByTelegram()); + } else { + channel->setVerifyDetails({}); + } if (!minimal && storiesState) { result->setStoriesState(!storiesState->maxId ? UserData::StoriesState::None @@ -2680,6 +2715,35 @@ void Session::unregisterDependentMessage( void Session::registerMessageRandomId(uint64 randomId, FullMsgId itemId) { _messageByRandomId.emplace(randomId, itemId); + + AssertIsDebug(); + if (peerIsChannel(itemId.peer)) { + if (const auto channel = channelLoaded(peerToChannel(itemId.peer))) { + auto colored = std::vector>(); + for (const auto &[id, document] : _documents) { + if (const auto sticker = document->sticker()) { + if (sticker->setType == Data::StickersType::Emoji) { + if (document->emojiUsesTextColor()) { + colored.push_back(document.get()); + } + } + } + } + const auto count = int(colored.size()); + if (count > 1) { + auto index1 = base::RandomIndex(count); + auto index2 = base::RandomIndex(count - 1); + if (index2 >= index1) { + ++index2; + } + channel->setVerifyDetails({ + .iconBgId = QString::number(colored[index1]->id), + .iconFgId = QString::number(colored[index2]->id), + }); + history(channel)->updateChatListEntry(); + } + } + } } void Session::unregisterMessageRandomId(uint64 randomId) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index eba5d0e8f..f24190c40 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -177,6 +177,8 @@ public: return ++_nonHistoryEntryId; } + [[nodiscard]] Ui::VerifyDetails verifiedByTelegram(); + void subscribeForTopicRepliesLists(); void clear(); @@ -1140,6 +1142,9 @@ private: const std::unique_ptr _businessInfo; std::unique_ptr _shortcutMessages; + QString _verifiedByTelegramIconBgId; + QString _verifiedByTelegramIconFgId; + MsgId _nonHistoryEntryId = ShortcutMaxMsgId; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 09d72a03e..01b51ec46 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -514,6 +514,21 @@ bool UserData::isUsernameEditable(QString username) const { return _username.isEditable(username); } +void UserData::setVerifyDetails(Ui::VerifyDetails details) { + if (!details) { + if (_verifyDetails) { + _verifyDetails = nullptr; + session().changes().peerUpdated(this, UpdateFlag::VerifyInfo); + } + } else if (!_verifyDetails) { + _verifyDetails = std::make_unique(details); + session().changes().peerUpdated(this, UpdateFlag::VerifyInfo); + } else if (*_verifyDetails != details) { + *_verifyDetails = details; + session().changes().peerUpdated(this, UpdateFlag::VerifyInfo); + } +} + const QString &UserData::phone() const { return _phone; } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 38a8c8d4b..b599f1ed3 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -177,6 +177,11 @@ public: [[nodiscard]] const std::vector &usernames() const; [[nodiscard]] bool isUsernameEditable(QString username) const; + void setVerifyDetails(Ui::VerifyDetails details); + [[nodiscard]] Ui::VerifyDetails *verifyDetails() const { + return _verifyDetails.get(); + } + enum class ContactStatus : char { Unknown, Contact, @@ -255,6 +260,7 @@ private: std::vector _unavailableReasons; QString _phone; QString _privateForwardName; + std::unique_ptr _verifyDetails; ChannelId _personalChannelId = 0; MsgId _personalChannelMessageId = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 7d3ed9e64..a5d322962 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -31,6 +31,12 @@ ThreeStateIcon { active: icon; } +VerifiedBadge { + bg: color; + fg: color; + height: pixels; +} + ForumTopicIcon { size: pixels; font: font; @@ -401,6 +407,22 @@ dialogsLockIcon: ThreeStateIcon { active: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedActive, point(4px, 0px) }}; } +dialogsVerifiedBg: icon{{ "dialogs/dialogs_verified_star", dialogsVerifiedIconBg }}; +dialogsVerifiedFg: icon{{ "dialogs/dialogs_verified_check", dialogsVerifiedIconFg }}; +dialogsVerifiedColors: VerifiedBadge { + height: 20px; + bg: dialogsVerifiedIconBg; + fg: dialogsVerifiedIconFg; +} +dialogsVerifiedColorsOver: VerifiedBadge(dialogsVerifiedColors) { + bg: dialogsVerifiedIconBgOver; + fg: dialogsVerifiedIconFgOver; +} +dialogsVerifiedColorsActive: VerifiedBadge(dialogsVerifiedColors) { + bg: dialogsVerifiedIconBgActive; + fg: dialogsVerifiedIconFgActive; +} + dialogsVerifiedIcon: icon { { "dialogs/dialogs_verified_star", dialogsVerifiedIconBg }, { "dialogs/dialogs_verified_check", dialogsVerifiedIconFg }, diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index a97decd0c..51dc9f11e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1379,12 +1379,17 @@ void InnerWidget::paintPeerSearchResult( Ui::NameTextOptions()); } - // draw chat icon - if (const auto chatTypeIcon = Ui::ChatTypeIcon(peer, context)) { - chatTypeIcon->paint(p, rectForName.topLeft(), context.width); - rectForName.setLeft(rectForName.left() - + chatTypeIcon->width() - + st::dialogsChatTypeSkip); + if (const auto details = peer->verifyDetails()) { + if (!result->badge.ready(details)) { + result->badge.set( + details, + peer->owner().customEmojiManager().factory(), + [=] { updateSearchResult(peer); }); + } + const auto &st = Ui::VerifiedStyle(context); + const auto position = rectForName.topLeft(); + const auto skip = result->badge.drawVerified(p, position, st); + rectForName.setLeft(position.x() + skip + st::dialogsChatTypeSkip); } const auto badgeWidth = result->badge.drawGetWidth( p, @@ -1461,112 +1466,6 @@ void InnerWidget::paintSearchTags( const auto position = QPoint(_searchTagsLeft, top); _searchTags->paint(p, position, context.now, context.paused); } -// -//void InnerWidget::paintSearchInChat( -// Painter &p, -// const Ui::PaintContext &context) const { -// auto height = searchInChatSkip(); -// -// auto top = 0; -// p.setFont(st::searchedBarFont); -// auto fullRect = QRect(0, top, width(), height - top); -// p.fillRect(fullRect, currentBg()); -// if (_searchFromShown) { -// p.setPen(st::dialogsTextFg); -// p.setTextPalette(st::dialogsSearchFromPalette); -// paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText); -// p.restoreTextPalette(); -// } -//} -// -//template -//void InnerWidget::paintSearchInFilter( -// Painter &p, -// PaintUserpic paintUserpic, -// int top, -// const style::icon *icon, -// const Ui::Text::String &text) const { -// const auto savedPen = p.pen(); -// const auto userpicLeft = st::defaultDialogRow.padding.left(); -// const auto userpicTop = top -// + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2; -// paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize); -// -// const auto nameleft = st::defaultDialogRow.padding.left() -// + st::dialogsSearchInPhotoSize -// + st::dialogsSearchInPhotoPadding; -// const auto namewidth = width() -// - nameleft -// - st::defaultDialogRow.padding.left() -// - st::defaultDialogRow.padding.right() -// - st::dialogsCancelSearch.width; -// auto rectForName = QRect( -// nameleft, -// top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2, -// namewidth, -// st::semiboldFont->height); -// if (icon) { -// icon->paint(p, rectForName.topLeft(), width()); -// rectForName.setLeft(rectForName.left() -// + icon->width() -// + st::dialogsChatTypeSkip); -// } -// p.setPen(savedPen); -// text.drawLeftElided( -// p, -// rectForName.left(), -// rectForName.top(), -// rectForName.width(), -// width()); -//} -// -//void InnerWidget::paintSearchInPeer( -// Painter &p, -// not_null peer, -// Ui::PeerUserpicView &userpic, -// int top, -// const Ui::Text::String &text) const { -// const auto paintUserpic = [&](Painter &p, int x, int y, int size) { -// peer->paintUserpicLeft(p, userpic, x, y, width(), size); -// }; -// const auto icon = Ui::ChatTypeIcon(peer); -// paintSearchInFilter(p, paintUserpic, top, icon, text); -//} -// -//void InnerWidget::paintSearchInSaved( -// Painter &p, -// int top, -// const Ui::Text::String &text) const { -// const auto paintUserpic = [&](Painter &p, int x, int y, int size) { -// Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size); -// }; -// paintSearchInFilter(p, paintUserpic, top, nullptr, text); -//} -// -//void InnerWidget::paintSearchInReplies( -// Painter &p, -// int top, -// const Ui::Text::String &text) const { -// const auto paintUserpic = [&](Painter &p, int x, int y, int size) { -// Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size); -// }; -// paintSearchInFilter(p, paintUserpic, top, nullptr, text); -//} -// -//void InnerWidget::paintSearchInTopic( -// Painter &p, -// const Ui::PaintContext &context, -// not_null topic, -// Ui::PeerUserpicView &userpic, -// int top, -// const Ui::Text::String &text) const { -// const auto paintUserpic = [&](Painter &p, int x, int y, int size) { -// p.translate(x, y); -// topic->paintUserpic(p, userpic, context); -// p.translate(-x, -y); -// }; -// paintSearchInFilter(p, paintUserpic, top, nullptr, text); -//} void InnerWidget::mouseMoveEvent(QMouseEvent *e) { if (_chatPreviewTouchGlobal || _touchDragStartGlobal) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index 3850b880e..f89a271d8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/text/text.h" #include "ui/effects/animations.h" +#include "ui/text/text.h" #include "ui/unread_badge.h" #include "ui/userpic_view.h" #include "dialogs/dialogs_key.h" diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 4de06e9f6..350ea38ba 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -449,13 +449,17 @@ void PaintRow( ? tr::lng_badge_psa_default(tr::now) : custom; PaintRowTopRight(p, text, rectForName, context); - } else if (from) { - if (const auto chatTypeIcon = ChatTypeIcon(from, context)) { - chatTypeIcon->paint(p, rectForName.topLeft(), context.width); - rectForName.setLeft(rectForName.left() - + chatTypeIcon->width() - + st::dialogsChatTypeSkip); + } else if (const auto details = from ? from->verifyDetails() : nullptr) { + if (!rowBadge.ready(details)) { + rowBadge.set( + details, + from->owner().customEmojiManager().factory(), + customEmojiRepaint); } + const auto &st = Ui::VerifiedStyle(context); + const auto position = rectForName.topLeft(); + const auto skip = rowBadge.drawVerified(p, position, st); + rectForName.setLeft(position.x() + skip + st::dialogsChatTypeSkip); } auto texttop = context.st->textTop; if (const auto folder = entry->asFolder()) { @@ -839,6 +843,14 @@ const style::icon *ChatTypeIcon( return nullptr; } +const style::VerifiedBadge &VerifiedStyle(const PaintContext &context) { + return context.active + ? st::dialogsVerifiedColorsActive + : context.selected + ? st::dialogsVerifiedColorsOver + : st::dialogsVerifiedColors; +} + void RowPainter::Paint( Painter &p, not_null row, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h index 914052447..e014e657f 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace style { struct DialogRow; +struct VerifiedBadge; } // namespace style namespace st { @@ -79,6 +80,9 @@ struct PaintContext { const PaintContext &context); [[nodiscard]] const style::icon *ChatTypeIcon(not_null peer); +[[nodiscard]] const style::VerifiedBadge &VerifiedStyle( + const PaintContext &context); + class RowPainter { public: static void Paint( diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 93c0e1ac2..0e2da0d49 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/window_peer_menu.h" #include "calls/calls_instance.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_peer_values.h" #include "data/data_group_call.h" // GroupCall::input. #include "data/data_folder.h" @@ -448,12 +449,14 @@ void TopBarWidget::paintTopBar(Painter &p) { return; } auto nameleft = _leftTaken; + auto statusleft = nameleft; auto nametop = st::topBarArrowPadding.top(); auto statustop = st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height; - auto availableWidth = width() + auto namewidth = width() - _rightTaken - nameleft - st::topBarNameRightPadding; + auto statuswidth = namewidth; if (_chooseForReportReason) { const auto text = _chooseForReportReason->optionText; @@ -489,7 +492,7 @@ void TopBarWidget::paintTopBar(Painter &p) { p, nameleft, nametop, - availableWidth); + namewidth); p.setFont(st::dialogsTextFont); if (!paintConnectingState(p, nameleft, statustop, width()) @@ -497,7 +500,7 @@ void TopBarWidget::paintTopBar(Painter &p) { p, nameleft, statustop, - availableWidth, + namewidth, width(), st::historyStatusFgTyping, now)) { @@ -524,8 +527,8 @@ void TopBarWidget::paintTopBar(Painter &p) { ? tr::lng_verification_codes(tr::now) : peer->name(); const auto textWidth = st::historySavedFont->width(text); - if (availableWidth < textWidth) { - text = st::historySavedFont->elided(text, availableWidth); + if (namewidth < textWidth) { + text = st::historySavedFont->elided(text, namewidth); } p.setPen(st::dialogsNameFg); p.setFont(st::historySavedFont); @@ -544,16 +547,16 @@ void TopBarWidget::paintTopBar(Painter &p) { tr::lng_manage_discussion_group(tr::now)); p.setFont(st::dialogsTextFont); - if (!paintConnectingState(p, nameleft, statustop, width()) + if (!paintConnectingState(p, statusleft, statustop, width()) && !paintSendAction( p, - nameleft, + statusleft, statustop, - availableWidth, + statuswidth, width(), st::historyStatusFgTyping, now)) { - paintStatus(p, nameleft, statustop, availableWidth, width()); + paintStatus(p, statusleft, statustop, statuswidth, width()); } } else if (namePeer) { if (_titleNameVersion < namePeer->nameVersion()) { @@ -563,12 +566,24 @@ void TopBarWidget::paintTopBar(Painter &p) { TopBarNameText(namePeer, _activeChat.section), Ui::NameTextOptions()); } + if (const auto details = namePeer->verifyDetails()) { + if (!_titleBadge.ready(details)) { + _titleBadge.set( + details, + namePeer->owner().customEmojiManager().factory(), + [=] { update(); }); + } + const auto position = QPoint{ nameleft, nametop }; + const auto skip = _titleBadge.drawVerified(p, position, st::dialogsVerifiedColors); + nameleft += skip + st::dialogsChatTypeSkip; + namewidth -= skip + st::dialogsChatTypeSkip; + } const auto badgeWidth = _titleBadge.drawGetWidth( p, QRect( nameleft, nametop, - availableWidth, + namewidth, st::msgNameStyle.font->height), _title.maxWidth(), width(), @@ -583,7 +598,7 @@ void TopBarWidget::paintTopBar(Painter &p) { .paused = _controller->isGifPausedAtLeastFor( Window::GifPauseReason::Any), }); - const auto namewidth = availableWidth - badgeWidth; + namewidth -= badgeWidth; p.setPen(st::dialogsNameFg); _title.draw(p, { @@ -593,16 +608,16 @@ void TopBarWidget::paintTopBar(Painter &p) { }); p.setFont(st::dialogsTextFont); - if (!paintConnectingState(p, nameleft, statustop, width()) + if (!paintConnectingState(p, statusleft, statustop, width()) && !paintSendAction( p, - nameleft, + statusleft, statustop, - availableWidth, + statuswidth, width(), st::historyStatusFgTyping, now)) { - paintStatus(p, nameleft, statustop, availableWidth, width()); + paintStatus(p, statusleft, statustop, statuswidth, width()); } } } diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 11648e9ad..f121010da 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -25,6 +25,7 @@ InfoPeerBadge { verified: icon; premium: icon; premiumFg: color; + premiumInnerFg: color; position: point; sizeTag: int; } @@ -439,6 +440,7 @@ infoPeerBadge: InfoPeerBadge { verified: infoVerifiedCheck; premium: infoPremiumStar; premiumFg: profileVerifiedCheckBg; + premiumInnerFg: profileVerifiedCheckFg; position: infoVerifiedCheckPosition; sizeTag: 1; // Large } diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp index 214c6e1cc..a2d15131e 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp @@ -113,14 +113,29 @@ void Badge::setContent(Content content) { switch (_content.badge) { case BadgeType::Verified: case BadgeType::Premium: { - if (const auto id = _content.emojiStatusId) { - _emojiStatus = _session->data().customEmojiManager().create( - id, - [raw = _view.data()] { raw->update(); }, - sizeTag()); - if (_customStatusLoopsLimit > 0) { + const auto id = _content.emojiStatusId; + const auto innerId = _content.emojiStatusInnerId; + if (id || innerId) { + _emojiStatus = id + ? _session->data().customEmojiManager().create( + id, + [raw = _view.data()] { raw->update(); }, + sizeTag()) + : nullptr; + _statusInner = innerId + ? _session->data().customEmojiManager().create( + innerId, + [raw = _view.data()] { raw->update(); }, + sizeTag()) + : nullptr; + if (_emojiStatus && _customStatusLoopsLimit > 0) { _emojiStatus = std::make_unique( - std::move(_emojiStatus), + std::move(_emojiStatus), + _customStatusLoopsLimit); + } + if (_statusInner && _customStatusLoopsLimit > 0) { + _statusInner = std::make_unique( + std::move(_statusInner), _customStatusLoopsLimit); } const auto emoji = Data::FrameSizeFromTag(sizeTag()) @@ -137,7 +152,13 @@ void Badge::setContent(Content content) { if (!_emojiStatusPanel || !_emojiStatusPanel->paintBadgeFrame(check)) { Painter p(check); - _emojiStatus->paint(p, args); + if (_emojiStatus) { + _emojiStatus->paint(p, args); + } + if (_statusInner) { + args.textColor = _st.premiumInnerFg->c; + _statusInner->paint(p, args); + } } }, _view->lifetime()); } else { diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.h b/Telegram/SourceFiles/info/profile/info_profile_badge.h index f0e770d3f..efff823cb 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_badge.h +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.h @@ -59,6 +59,7 @@ public: struct Content { BadgeType badge = BadgeType::None; DocumentId emojiStatusId = 0; + DocumentId emojiStatusInnerId = 0; friend inline constexpr bool operator==(Content, Content) = default; }; @@ -92,6 +93,7 @@ private: EmojiStatusPanel *_emojiStatusPanel = nullptr; const int _customStatusLoopsLimit = 0; std::unique_ptr _emojiStatus; + std::unique_ptr _statusInner; base::flags _allowed; Content _content; Fn _premiumClickCallback; diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index c91b146a5..b935189e4 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -293,6 +293,21 @@ Cover::Cover( std::move(title)) { } +[[nodiscard]] rpl::producer VerifyBadgeForPeer( + not_null peer) { + return peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::VerifyInfo + ) | rpl::map([=] { + const auto details = peer->verifyDetails(); + return Badge::Content{ + .badge = details ? BadgeType::Verified : BadgeType::None, + .emojiStatusId = details->iconBgId.toULongLong(), + .emojiStatusInnerId = details->iconFgId.toULongLong(), + }; + }); +} + Cover::Cover( QWidget *parent, not_null controller, @@ -308,6 +323,17 @@ Cover::Cover( , _emojiStatusPanel(peer->isSelf() ? std::make_unique() : nullptr) +, _verify( + std::make_unique( + this, + st::infoPeerBadge, + &peer->session(), + VerifyBadgeForPeer(peer), + nullptr, + [=] { + return controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); + })) , _badge( std::make_unique( this, @@ -359,7 +385,10 @@ Cover::Cover( ::Settings::ShowEmojiStatusPremium(_controller, _peer); } }); - _badge->updated() | rpl::start_with_next([=] { + rpl::merge( + _verify->updated(), + _badge->updated() + ) | rpl::start_with_next([=] { refreshNameGeometry(width()); }, _name->lifetime()); @@ -699,11 +728,17 @@ void Cover::refreshNameGeometry(int newWidth) { if (const auto widget = _badge->widget()) { nameWidth -= st::infoVerifiedCheckPosition.x() + widget->width(); } - _name->resizeToNaturalWidth(nameWidth); - _name->moveToLeft(_st.nameLeft, _st.nameTop, newWidth); - const auto badgeLeft = _st.nameLeft + _name->width(); + auto nameLeft = _st.nameLeft; const auto badgeTop = _st.nameTop; const auto badgeBottom = _st.nameTop + _name->height(); + _verify->move(nameLeft, badgeTop, badgeBottom); + if (const auto widget = _verify->widget()) { + nameLeft += widget->width() + st::infoVerifiedCheckPosition.x(); + nameWidth -= widget->width() + st::infoVerifiedCheckPosition.x(); + } + _name->resizeToNaturalWidth(nameWidth); + _name->moveToLeft(nameLeft, _st.nameTop, newWidth); + const auto badgeLeft = nameLeft + _name->width(); _badge->move(badgeLeft, badgeTop, badgeBottom); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index c4f5a73ae..f3404f977 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -147,6 +147,7 @@ private: const not_null _controller; const not_null _peer; const std::unique_ptr _emojiStatusPanel; + const std::unique_ptr _verify; const std::unique_ptr _badge; rpl::variable _onlineCount; diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index fba586e0d..0f04feee2 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -659,8 +659,6 @@ rpl::producer BadgeValueFromFlags(Peer peer) { ? BadgeType::Scam : (value & Flag::Fake) ? BadgeType::Fake - : (value & Flag::Verified) - ? BadgeType::Verified : premium ? BadgeType::Premium : BadgeType::None; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index a64cc6e92..4453a307e 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -203,6 +203,7 @@ settingsInfoPeerBadge: InfoPeerBadge { }; premium: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBg }}; premiumFg: dialogsVerifiedIconBg; + premiumInnerFg: dialogsVerifiedIconFg; sizeTag: 0; // Normal } diff --git a/Telegram/SourceFiles/ui/unread_badge.cpp b/Telegram/SourceFiles/ui/unread_badge.cpp index 375beb5c6..dee9af528 100644 --- a/Telegram/SourceFiles/ui/unread_badge.cpp +++ b/Telegram/SourceFiles/ui/unread_badge.cpp @@ -31,6 +31,13 @@ struct PeerBadge::EmojiStatus { int skip = 0; }; +struct PeerBadge::VerifiedData { + QImage cache; + QImage cacheFg; + std::unique_ptr bg; + std::unique_ptr fg; +}; + void UnreadBadge::setText(const QString &text, bool active) { _text = text; _active = active; @@ -193,14 +200,6 @@ int PeerBadge::drawGetWidth( .paused = descriptor.paused || On(PowerSaving::kEmojiStatus), }); return iconw - 4 * _emojiStatus->skip; - } else if (descriptor.verified && peer->isVerified()) { - const auto iconw = descriptor.verified->width(); - descriptor.verified->paint( - p, - rectForName.x() + qMin(nameWidth, rectForName.width() - iconw), - rectForName.y(), - outerWidth); - return iconw; } else if (descriptor.premium && peer->isPremium() && peer->session().premiumBadgesShown()) { @@ -219,4 +218,70 @@ void PeerBadge::unload() { _emojiStatus = nullptr; } +bool PeerBadge::ready(const VerifyDetails *details) const { + if (!details || !*details) { + _verifiedData = nullptr; + return true; + } else if (!_verifiedData) { + return false; + } + if (details->iconBgId.isEmpty()) { + _verifiedData->bg = nullptr; + } else if (!_verifiedData->bg + || _verifiedData->bg->entityData() != details->iconBgId) { + return false; + } + if (details->iconFgId.isEmpty()) { + _verifiedData->fg = nullptr; + } else if (!_verifiedData->fg + || _verifiedData->fg->entityData() != details->iconFgId) { + return false; + } + return true; +} + +void PeerBadge::set( + not_null details, + Ui::Text::CustomEmojiFactory factory, + Fn repaint) { + if (!_verifiedData) { + _verifiedData = std::make_unique(); + } + if (!details->iconBgId.isEmpty()) { + _verifiedData->bg = factory(details->iconBgId, repaint); + } + if (!details->iconFgId.isEmpty()) { + _verifiedData->fg = factory(details->iconFgId, repaint); + } +} + +int PeerBadge::drawVerified( + QPainter &p, + QPoint position, + const style::VerifiedBadge &st) { + const auto data = _verifiedData.get(); + if (!data) { + return 0; + } + const auto now = crl::now(); + auto result = 0; + if (const auto bg = data->bg.get()) { + bg->paint(p, { + .textColor = st.bg->c, + .now = now, + .position = position, + }); + result = bg->width(); + } + if (const auto fg = data->fg.get()) { + fg->paint(p, { + .textColor = st.fg->c, + .now = now, + .position = position, + }); + result = std::max(result, fg->width()); + } + return result; +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/unread_badge.h b/Telegram/SourceFiles/ui/unread_badge.h index 4172330ef..0b1cd6015 100644 --- a/Telegram/SourceFiles/ui/unread_badge.h +++ b/Telegram/SourceFiles/ui/unread_badge.h @@ -7,11 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "ui/text/text_custom_emoji.h" #include "ui/rp_widget.h" -namespace Ui::Text { -class CustomEmoji; -} // namespace Ui::Text +namespace style { +struct VerifiedBadge; +} // namespace style namespace Ui { @@ -31,6 +32,19 @@ private: }; +struct VerifyDetails { + QString iconBgId; + QString iconFgId; + TextWithEntities description; + + explicit operator bool() const { + return !iconBgId.isEmpty() || !iconFgId.isEmpty(); + } + friend inline bool operator==( + const VerifyDetails &, + const VerifyDetails &) = default; +}; + class PeerBadge { public: PeerBadge(); @@ -54,9 +68,24 @@ public: const Descriptor &descriptor); void unload(); + [[nodiscard]] bool ready(const VerifyDetails *details) const; + void set( + not_null details, + Text::CustomEmojiFactory factory, + Fn repaint); + + // How much horizontal space the badge took. + int drawVerified( + QPainter &p, + QPoint position, + const style::VerifiedBadge &st); + private: struct EmojiStatus; + struct VerifiedData; + std::unique_ptr _emojiStatus; + mutable std::unique_ptr _verifiedData; };