/* 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 "ui/unread_badge.h" #include "data/data_emoji_statuses.h" #include "data/data_peer.h" #include "data/data_user.h" #include "data/data_session.h" #include "data/stickers/data_custom_emoji.h" #include "main/main_session.h" #include "lang/lang_keys.h" #include "ui/painter.h" #include "ui/power_saving.h" #include "ui/unread_badge_paint.h" #include "styles/style_dialogs.h" // AyuGram includes #include "ayu/utils/telegram_helpers.h" namespace Ui { namespace { constexpr auto kPlayStatusLimit = 12; } // namespace struct PeerBadge::EmojiStatus { EmojiStatusId id; std::unique_ptr emoji; int skip = 0; }; struct PeerBadge::BotVerifiedData { QImage cache; std::unique_ptr icon; }; void UnreadBadge::setText(const QString &text, bool active) { _text = text; _active = active; const auto st = Dialogs::Ui::UnreadBadgeStyle(); resize( std::max(st.font->width(text) + 2 * st.padding, st.size), st.size); update(); } int UnreadBadge::textBaseline() const { const auto st = Dialogs::Ui::UnreadBadgeStyle(); return ((st.size - st.font->height) / 2) + st.font->ascent; } void UnreadBadge::paintEvent(QPaintEvent *e) { if (_text.isEmpty()) { return; } auto p = QPainter(this); UnreadBadgeStyle unreadSt; unreadSt.muted = !_active; auto unreadRight = width(); auto unreadTop = 0; PaintUnreadBadge( p, _text, unreadRight, unreadTop, unreadSt); } QSize ScamBadgeSize(bool fake) { const auto phrase = fake ? tr::lng_fake_badge(tr::now) : tr::lng_scam_badge(tr::now); const auto phraseWidth = st::dialogsScamFont->width(phrase); const auto width = st::dialogsScamPadding.left() + phraseWidth + st::dialogsScamPadding.right(); const auto height = st::dialogsScamPadding.top() + st::dialogsScamFont->height + st::dialogsScamPadding.bottom(); return { width, height }; } void DrawScamFakeBadge( Painter &p, QRect rect, int outerWidth, const style::color &color, const QString &phrase, int phraseWidth) { PainterHighQualityEnabler hq(p); auto pen = color->p; pen.setWidth(st::lineWidth); p.setPen(pen); p.setBrush(Qt::NoBrush); p.drawRoundedRect(rect, st::dialogsScamRadius, st::dialogsScamRadius); p.setFont(st::dialogsScamFont); p.drawTextLeft( rect.x() + st::dialogsScamPadding.left(), rect.y() + st::dialogsScamPadding.top(), outerWidth, phrase, phraseWidth); } void DrawScamBadge( bool fake, Painter &p, QRect rect, int outerWidth, const style::color &color) { const auto phrase = fake ? tr::lng_fake_badge(tr::now) : tr::lng_scam_badge(tr::now); DrawScamFakeBadge( p, rect, outerWidth, color, phrase, st::dialogsScamFont->width(phrase)); } PeerBadge::PeerBadge() = default; PeerBadge::~PeerBadge() = default; int PeerBadge::drawGetWidth(Painter &p, Descriptor &&descriptor) { Expects(descriptor.customEmojiRepaint != nullptr); const auto peer = descriptor.peer; if (descriptor.scam && (peer->isScam() || peer->isFake())) { return drawScamOrFake(p, descriptor); } const auto verifyCheck = descriptor.verified && peer->isVerified(); const auto premiumMark = descriptor.premium && peer->session().premiumBadgesShown(); const auto emojiStatus = premiumMark && peer->emojiStatusId() && (peer->isPremium() || peer->isChannel()); const auto premiumStar = premiumMark && !emojiStatus && peer->isPremium(); const auto paintVerify = verifyCheck && (descriptor.prioritizeVerification || descriptor.bothVerifyAndStatus || !emojiStatus); const auto paintEmoji = emojiStatus && (!paintVerify || descriptor.bothVerifyAndStatus); const auto paintStar = premiumStar && !paintVerify; const auto paintExteraDev = isExteraPeer(getBareID(peer)) && (!paintEmoji || descriptor.bothVerifyAndStatus); const auto paintExteraSupporter = !paintExteraDev && isSupporterPeer(getBareID(peer)) && (!paintEmoji || descriptor.bothVerifyAndStatus); const auto exteraWidth = paintExteraDev ? descriptor.exteraOfficial->width() : paintExteraSupporter ? descriptor.exteraSupporter->width() : 0; auto result = 0; if (paintEmoji) { auto &rectForName = descriptor.rectForName; const auto verifyWidth = descriptor.verified->width(); if (paintVerify) { rectForName.setWidth(rectForName.width() - verifyWidth); } if (paintExteraDev || paintExteraSupporter) { rectForName.setWidth(rectForName.width() - exteraWidth); } result += drawPremiumEmojiStatus(p, descriptor); if (!paintVerify && !paintExteraDev) { return result; } if (paintVerify) { rectForName.setWidth(rectForName.width() + verifyWidth); } if (paintExteraDev || paintExteraSupporter) { rectForName.setWidth(rectForName.width() + exteraWidth); } descriptor.nameWidth += result; } if (paintExteraDev || paintExteraSupporter) { if (paintStar) { auto &rectForName = descriptor.rectForName; rectForName.setWidth(rectForName.width() - exteraWidth); result += drawPremiumStar(p, descriptor); rectForName.setWidth(rectForName.width() + exteraWidth); descriptor.nameWidth += result; } result += paintExteraDev ? drawExteraOfficial(p, descriptor) : drawExteraSupporter(p, descriptor); return result; } if (paintVerify) { result += drawVerifyCheck(p, descriptor); return result; } else if (paintStar) { return drawPremiumStar(p, descriptor); } return 0; } int PeerBadge::drawScamOrFake(Painter &p, const Descriptor &descriptor) { const auto phrase = descriptor.peer->isScam() ? tr::lng_scam_badge(tr::now) : tr::lng_fake_badge(tr::now); const auto phraseWidth = st::dialogsScamFont->width(phrase); const auto width = st::dialogsScamPadding.left() + phraseWidth + st::dialogsScamPadding.right(); const auto height = st::dialogsScamPadding.top() + st::dialogsScamFont->height + st::dialogsScamPadding.bottom(); const auto rectForName = descriptor.rectForName; const auto rect = QRect( (rectForName.x() + qMin( descriptor.nameWidth + st::dialogsScamSkip, rectForName.width() - width)), rectForName.y() + (rectForName.height() - height) / 2, width, height); DrawScamFakeBadge( p, rect, descriptor.outerWidth, *descriptor.scam, phrase, phraseWidth); return st::dialogsScamSkip + width; } int PeerBadge::drawVerifyCheck(Painter &p, const Descriptor &descriptor) { const auto iconw = descriptor.verified->width(); const auto rectForName = descriptor.rectForName; const auto nameWidth = descriptor.nameWidth; descriptor.verified->paint( p, rectForName.x() + qMin(nameWidth, rectForName.width() - iconw), rectForName.y(), descriptor.outerWidth); return iconw; } int PeerBadge::drawPremiumEmojiStatus( Painter &p, const Descriptor &descriptor) { const auto peer = descriptor.peer; const auto id = peer->emojiStatusId(); const auto rectForName = descriptor.rectForName; const auto iconw = descriptor.premium->width(); const auto iconx = rectForName.x() + qMin(descriptor.nameWidth, rectForName.width() - iconw); const auto icony = rectForName.y(); if (!_emojiStatus) { _emojiStatus = std::make_unique(); const auto size = st::emojiSize; const auto emoji = Ui::Text::AdjustCustomEmojiSize(size); _emojiStatus->skip = (size - emoji) / 2; } if (_emojiStatus->id != id) { using namespace Ui::Text; auto &manager = peer->session().data().customEmojiManager(); _emojiStatus->id = id; _emojiStatus->emoji = std::make_unique( manager.create( Data::EmojiStatusCustomId(id), descriptor.customEmojiRepaint), kPlayStatusLimit); } _emojiStatus->emoji->paint(p, { .textColor = (*descriptor.premiumFg)->c, .now = descriptor.now, .position = QPoint( iconx - 2 * _emojiStatus->skip, icony + _emojiStatus->skip), .paused = descriptor.paused || On(PowerSaving::kEmojiStatus), }); return iconw - 4 * _emojiStatus->skip; } int PeerBadge::drawPremiumStar(Painter &p, const Descriptor &descriptor) { const auto rectForName = descriptor.rectForName; const auto iconw = descriptor.premium->width(); const auto iconx = rectForName.x() + qMin(descriptor.nameWidth, rectForName.width() - iconw); const auto icony = rectForName.y(); _emojiStatus = nullptr; descriptor.premium->paint(p, iconx, icony, descriptor.outerWidth); return iconw; } int PeerBadge::drawExteraOfficial(Painter &p, const Descriptor &descriptor) { const auto iconw = descriptor.exteraOfficial->width(); const auto rectForName = descriptor.rectForName; const auto nameWidth = descriptor.nameWidth; descriptor.exteraOfficial->paint( p, rectForName.x() + qMin(nameWidth, rectForName.width() - iconw), rectForName.y(), descriptor.outerWidth); return iconw; } int PeerBadge::drawExteraSupporter(Painter &p, const Descriptor &descriptor) { const auto iconw = descriptor.exteraSupporter->width(); const auto rectForName = descriptor.rectForName; const auto nameWidth = descriptor.nameWidth; descriptor.exteraSupporter->paint( p, rectForName.x() + qMin(nameWidth, rectForName.width() - iconw), rectForName.y(), descriptor.outerWidth); return iconw; } void PeerBadge::unload() { _emojiStatus = nullptr; } bool PeerBadge::ready(const BotVerifyDetails *details) const { if (!details || !*details) { _botVerifiedData = nullptr; return true; } else if (!_botVerifiedData) { return false; } if (!details->iconId) { _botVerifiedData->icon = nullptr; } else if (!_botVerifiedData->icon || (_botVerifiedData->icon->entityData() != Data::SerializeCustomEmojiId(details->iconId))) { return false; } return true; } void PeerBadge::set( not_null details, Ui::Text::CustomEmojiFactory factory, Fn repaint) { if (!_botVerifiedData) { _botVerifiedData = std::make_unique(); } if (details->iconId) { _botVerifiedData->icon = std::make_unique( factory( Data::SerializeCustomEmojiId(details->iconId), { .repaint = repaint })); } } int PeerBadge::drawVerified( QPainter &p, QPoint position, const style::VerifiedBadge &st) { const auto data = _botVerifiedData.get(); if (!data) { return 0; } if (const auto icon = data->icon.get()) { icon->paint(p, { .textColor = st.color->c, .now = crl::now(), .position = position, }); return icon->width(); } return 0; } } // namespace Ui