Proof-of-concept custom verify badges.

This commit is contained in:
John Preston 2024-12-10 20:59:32 +04:00
parent 35e40be550
commit 6f18b9b691
25 changed files with 437 additions and 195 deletions

View file

@ -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

View file

@ -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<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -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<Ui::VerifyDetails>(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;

View file

@ -376,6 +376,11 @@ public:
[[nodiscard]] bool canRestrictParticipant(
not_null<PeerData*> 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<Data::GroupCall> _call;
PeerId _callDefaultJoinAs = 0;
std::unique_ptr<Ui::VerifyDetails> _verifyDetails;
};
namespace Data {

View file

@ -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();

View file

@ -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<QChar> _nameFirstLetters;
DocumentId _emojiStatusId = 0;
uint64 _backgroundEmojiId = 0;
DocumentId _backgroundEmojiId = 0;
crl::time _lastFullUpdate = 0;
QString _name;

View file

@ -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 <QtCore/QBuffer>
namespace Data {
namespace {
@ -329,6 +335,25 @@ Session::Session(not_null<Main::Session*> 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<UserData*> 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<PeerData*> 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<not_null<DocumentData*>>();
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) {

View file

@ -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> _businessInfo;
std::unique_ptr<ShortcutMessages> _shortcutMessages;
QString _verifiedByTelegramIconBgId;
QString _verifiedByTelegramIconFgId;
MsgId _nonHistoryEntryId = ShortcutMaxMsgId;
rpl::lifetime _lifetime;

View file

@ -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<Ui::VerifyDetails>(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;
}

View file

@ -177,6 +177,11 @@ public:
[[nodiscard]] const std::vector<QString> &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<Data::UnavailableReason> _unavailableReasons;
QString _phone;
QString _privateForwardName;
std::unique_ptr<Ui::VerifyDetails> _verifyDetails;
ChannelId _personalChannelId = 0;
MsgId _personalChannelMessageId = 0;

View file

@ -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 },

View file

@ -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 <typename PaintUserpic>
//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<PeerData*> 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<Data::ForumTopic*> 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) {

View file

@ -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"

View file

@ -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<const Row*> row,

View file

@ -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<PeerData*> peer);
[[nodiscard]] const style::VerifiedBadge &VerifiedStyle(
const PaintContext &context);
class RowPainter {
public:
static void Paint(

View file

@ -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());
}
}
}

View file

@ -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
}

View file

@ -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<Ui::Text::LimitedLoopsEmoji>(
std::move(_emojiStatus),
std::move(_emojiStatus),
_customStatusLoopsLimit);
}
if (_statusInner && _customStatusLoopsLimit > 0) {
_statusInner = std::make_unique<Ui::Text::LimitedLoopsEmoji>(
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 {

View file

@ -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<Ui::Text::CustomEmoji> _emojiStatus;
std::unique_ptr<Ui::Text::CustomEmoji> _statusInner;
base::flags<BadgeType> _allowed;
Content _content;
Fn<void()> _premiumClickCallback;

View file

@ -293,6 +293,21 @@ Cover::Cover(
std::move(title)) {
}
[[nodiscard]] rpl::producer<Badge::Content> VerifyBadgeForPeer(
not_null<PeerData*> 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<Window::SessionController*> controller,
@ -308,6 +323,17 @@ Cover::Cover(
, _emojiStatusPanel(peer->isSelf()
? std::make_unique<EmojiStatusPanel>()
: nullptr)
, _verify(
std::make_unique<Badge>(
this,
st::infoPeerBadge,
&peer->session(),
VerifyBadgeForPeer(peer),
nullptr,
[=] {
return controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
}))
, _badge(
std::make_unique<Badge>(
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);
}

View file

@ -147,6 +147,7 @@ private:
const not_null<Window::SessionController*> _controller;
const not_null<PeerData*> _peer;
const std::unique_ptr<EmojiStatusPanel> _emojiStatusPanel;
const std::unique_ptr<Badge> _verify;
const std::unique_ptr<Badge> _badge;
rpl::variable<int> _onlineCount;

View file

@ -659,8 +659,6 @@ rpl::producer<BadgeType> BadgeValueFromFlags(Peer peer) {
? BadgeType::Scam
: (value & Flag::Fake)
? BadgeType::Fake
: (value & Flag::Verified)
? BadgeType::Verified
: premium
? BadgeType::Premium
: BadgeType::None;

View file

@ -203,6 +203,7 @@ settingsInfoPeerBadge: InfoPeerBadge {
};
premium: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBg }};
premiumFg: dialogsVerifiedIconBg;
premiumInnerFg: dialogsVerifiedIconFg;
sizeTag: 0; // Normal
}

View file

@ -31,6 +31,13 @@ struct PeerBadge::EmojiStatus {
int skip = 0;
};
struct PeerBadge::VerifiedData {
QImage cache;
QImage cacheFg;
std::unique_ptr<Text::CustomEmoji> bg;
std::unique_ptr<Text::CustomEmoji> 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<const VerifyDetails*> details,
Ui::Text::CustomEmojiFactory factory,
Fn<void()> repaint) {
if (!_verifiedData) {
_verifiedData = std::make_unique<VerifiedData>();
}
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

View file

@ -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<const VerifyDetails*> details,
Text::CustomEmojiFactory factory,
Fn<void()> 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> _emojiStatus;
mutable std::unique_ptr<VerifiedData> _verifiedData;
};