Show both verify and status emoji in opened chat.

This commit is contained in:
John Preston 2025-01-06 11:59:40 +04:00
parent ecf9faa21d
commit 549de7fa54
8 changed files with 265 additions and 206 deletions

View file

@ -788,31 +788,29 @@ int PeerListRow::paintNameIconGetWidth(
|| _isVerifyCodesChat) { || _isVerifyCodesChat) {
return 0; return 0;
} }
return _badge.drawGetWidth( return _badge.drawGetWidth(p, {
p, .peer = peer(),
QRect( .rectForName = QRect(
nameLeft, nameLeft,
nameTop, nameTop,
availableWidth, availableWidth,
st::semiboldFont->height), st::semiboldFont->height),
nameWidth, .nameWidth = nameWidth,
outerWidth, .outerWidth = outerWidth,
{ .verified = &(selected
.peer = peer(), ? st::dialogsVerifiedIconOver
.verified = &(selected : st::dialogsVerifiedIcon),
? st::dialogsVerifiedIconOver .premium = &(selected
: st::dialogsVerifiedIcon), ? st::dialogsPremiumIcon.over
.premium = &(selected : st::dialogsPremiumIcon.icon),
? st::dialogsPremiumIcon.over .scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
: st::dialogsPremiumIcon.icon), .premiumFg = &(selected
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg), ? st::dialogsVerifiedIconBgOver
.premiumFg = &(selected : st::dialogsVerifiedIconBg),
? st::dialogsVerifiedIconBgOver .customEmojiRepaint = repaint,
: st::dialogsVerifiedIconBg), .now = now,
.customEmojiRepaint = repaint, .paused = false,
.now = now, });
.paused = false,
});
} }
void PeerListRow::paintStatusText( void PeerListRow::paintStatusText(

View file

@ -1447,36 +1447,35 @@ void InnerWidget::paintPeerSearchResult(
+ chatTypeIcon->width() + chatTypeIcon->width()
+ st::dialogsChatTypeSkip); + st::dialogsChatTypeSkip);
} }
const auto badgeWidth = result->badge.drawGetWidth( const auto badgeWidth = result->badge.drawGetWidth(p, {
p, .peer = peer,
rectForName, .rectForName = rectForName,
result->name.maxWidth(), .nameWidth = result->name.maxWidth(),
context.width, .outerWidth = context.width,
{ .verified = (context.active
.peer = peer, ? &st::dialogsVerifiedIconActive
.verified = (context.active : context.selected
? &st::dialogsVerifiedIconActive ? &st::dialogsVerifiedIconOver
: context.selected : &st::dialogsVerifiedIcon),
? &st::dialogsVerifiedIconOver .premium = &ThreeStateIcon(
: &st::dialogsVerifiedIcon), st::dialogsPremiumIcon,
.premium = &ThreeStateIcon( context.active,
st::dialogsPremiumIcon, context.selected),
context.active, .scam = (context.active
context.selected), ? &st::dialogsScamFgActive
.scam = (context.active : context.selected
? &st::dialogsScamFgActive ? &st::dialogsScamFgOver
: context.selected : &st::dialogsScamFg),
? &st::dialogsScamFgOver .premiumFg = (context.active
: &st::dialogsScamFg), ? &st::dialogsVerifiedIconBgActive
.premiumFg = (context.active : context.selected
? &st::dialogsVerifiedIconBgActive ? &st::dialogsVerifiedIconBgOver
: context.selected : &st::dialogsVerifiedIconBg),
? &st::dialogsVerifiedIconBgOver .customEmojiRepaint = [=] { updateSearchResult(peer); },
: &st::dialogsVerifiedIconBg), .now = context.now,
.customEmojiRepaint = [=] { updateSearchResult(peer); }, .prioritizeVerification = true,
.now = context.now, .paused = context.paused,
.paused = context.paused, });
});
rectForName.setWidth(rectForName.width() - badgeWidth); rectForName.setWidth(rectForName.width() - badgeWidth);
QRect tr(context.st->textLeft, context.st->textTop, namewidth, st::dialogsTextFont->height); QRect tr(context.st->textLeft, context.st->textTop, namewidth, st::dialogsTextFont->height);

View file

@ -729,39 +729,41 @@ void PaintRow(
: context.selected : context.selected
? st::dialogsNameFgOver ? st::dialogsNameFgOver
: st::dialogsNameFg); : st::dialogsNameFg);
p.drawTextLeft(rectForName.left(), rectForName.top(), context.width, text); p.drawTextLeft(
rectForName.left(),
rectForName.top(),
context.width,
text);
} else if (from) { } else if (from) {
if ((history || sublist) && !context.search) { if ((history || sublist) && !context.search) {
const auto badgeWidth = rowBadge.drawGetWidth( const auto badgeWidth = rowBadge.drawGetWidth(p, {
p, .peer = from,
rectForName, .rectForName = rectForName,
rowName.maxWidth(), .nameWidth = rowName.maxWidth(),
context.width, .outerWidth = context.width,
{ .verified = (context.active
.peer = from, ? &st::dialogsVerifiedIconActive
.verified = (context.active : context.selected
? &st::dialogsVerifiedIconActive ? &st::dialogsVerifiedIconOver
: context.selected : &st::dialogsVerifiedIcon),
? &st::dialogsVerifiedIconOver .premium = &ThreeStateIcon(
: &st::dialogsVerifiedIcon), st::dialogsPremiumIcon,
.premium = &ThreeStateIcon( context.active,
st::dialogsPremiumIcon, context.selected),
context.active, .scam = (context.active
context.selected), ? &st::dialogsScamFgActive
.scam = (context.active : context.selected
? &st::dialogsScamFgActive ? &st::dialogsScamFgOver
: context.selected : &st::dialogsScamFg),
? &st::dialogsScamFgOver .premiumFg = (context.active
: &st::dialogsScamFg), ? &st::dialogsVerifiedIconBgActive
.premiumFg = (context.active : context.selected
? &st::dialogsVerifiedIconBgActive ? &st::dialogsVerifiedIconBgOver
: context.selected : &st::dialogsVerifiedIconBg),
? &st::dialogsVerifiedIconBgOver .customEmojiRepaint = customEmojiRepaint,
: &st::dialogsVerifiedIconBg), .now = context.now,
.customEmojiRepaint = customEmojiRepaint, .paused = context.paused,
.now = context.now, });
.paused = context.paused,
});
rectForName.setWidth(rectForName.width() - badgeWidth); rectForName.setWidth(rectForName.width() - badgeWidth);
} }
p.setPen(context.active p.setPen(context.active

View file

@ -578,26 +578,25 @@ void TopBarWidget::paintTopBar(Painter &p) {
nameleft += skip + st::dialogsChatTypeSkip; nameleft += skip + st::dialogsChatTypeSkip;
namewidth -= skip + st::dialogsChatTypeSkip; namewidth -= skip + st::dialogsChatTypeSkip;
} }
const auto badgeWidth = _titleBadge.drawGetWidth( const auto badgeWidth = _titleBadge.drawGetWidth(p, {
p, .peer = namePeer,
QRect( .rectForName = QRect(
nameleft, nameleft,
nametop, nametop,
namewidth, namewidth,
st::msgNameStyle.font->height), st::msgNameStyle.font->height),
_title.maxWidth(), .nameWidth = _title.maxWidth(),
width(), .outerWidth = width(),
{ .verified = &st::dialogsVerifiedIcon,
.peer = namePeer, .premium = &st::dialogsPremiumIcon.icon,
.verified = &st::dialogsVerifiedIcon, .scam = &st::attentionButtonFg,
.premium = &st::dialogsPremiumIcon.icon, .premiumFg = &st::dialogsVerifiedIconBg,
.scam = &st::attentionButtonFg, .customEmojiRepaint = [=] { update(); },
.premiumFg = &st::dialogsVerifiedIconBg, .now = now,
.customEmojiRepaint = [=] { update(); }, .bothVerifyAndStatus = true,
.now = now, .paused = _controller->isGifPausedAtLeastFor(
.paused = _controller->isGifPausedAtLeastFor( Window::GifPauseReason::Any),
Window::GifPauseReason::Any), });
});
namewidth -= badgeWidth; namewidth -= badgeWidth;
p.setPen(st::dialogsNameFg); p.setPen(st::dialogsNameFg);

View file

@ -440,7 +440,7 @@ infoPeerBadge: InfoPeerBadge {
premium: infoPremiumStar; premium: infoPremiumStar;
premiumFg: profileVerifiedCheckBg; premiumFg: profileVerifiedCheckBg;
position: infoVerifiedCheckPosition; position: infoVerifiedCheckPosition;
sizeTag: 0; // Large sizeTag: 0; // Normal
} }
infoIconFg: windowBoldFg; infoIconFg: windowBoldFg;

View file

@ -39,6 +39,11 @@ namespace {
}); });
} }
[[nodiscard]] bool HasPremiumClick(const Badge::Content &content) {
return content.badge == BadgeType::Premium
|| (content.badge == BadgeType::Verified && content.emojiStatusId);
}
} // namespace } // namespace
Badge::Badge( Badge::Badge(
@ -114,6 +119,15 @@ void Badge::setContent(Content content) {
case BadgeType::Verified: case BadgeType::Verified:
case BadgeType::Premium: { case BadgeType::Premium: {
const auto id = _content.emojiStatusId; const auto id = _content.emojiStatusId;
const auto emoji = id
? (Data::FrameSizeFromTag(sizeTag())
/ style::DevicePixelRatio())
: 0;
const auto icon = (_content.badge == BadgeType::Verified)
? &_st.verified
: id
? nullptr
: &_st.premium;
if (id) { if (id) {
_emojiStatus = _session->data().customEmojiManager().create( _emojiStatus = _session->data().customEmojiManager().create(
id, id,
@ -121,14 +135,16 @@ void Badge::setContent(Content content) {
sizeTag()); sizeTag());
if (_customStatusLoopsLimit > 0) { if (_customStatusLoopsLimit > 0) {
_emojiStatus = std::make_unique<Ui::Text::LimitedLoopsEmoji>( _emojiStatus = std::make_unique<Ui::Text::LimitedLoopsEmoji>(
std::move(_emojiStatus), std::move(_emojiStatus),
_customStatusLoopsLimit); _customStatusLoopsLimit);
} }
const auto emoji = Data::FrameSizeFromTag(sizeTag()) }
/ style::DevicePixelRatio(); const auto width = emoji + (icon ? icon->width() : 0);
_view->resize(emoji, emoji); const auto height = std::max(emoji, icon ? icon->height() : 0);
_view->paintRequest( _view->resize(width, height);
) | rpl::start_with_next([=, check = _view.data()]{ _view->paintRequest(
) | rpl::start_with_next([=, check = _view.data()]{
if (_emojiStatus) {
auto args = Ui::Text::CustomEmoji::Context{ auto args = Ui::Text::CustomEmoji::Context{
.textColor = _st.premiumFg->c, .textColor = _st.premiumFg->c,
.now = crl::now(), .now = crl::now(),
@ -140,18 +156,12 @@ void Badge::setContent(Content content) {
Painter p(check); Painter p(check);
_emojiStatus->paint(p, args); _emojiStatus->paint(p, args);
} }
}, _view->lifetime()); }
} else { if (icon) {
const auto icon = (_content.badge == BadgeType::Verified)
? &_st.verified
: &_st.premium;
_view->resize(icon->size());
_view->paintRequest(
) | rpl::start_with_next([=, check = _view.data()]{
Painter p(check); Painter p(check);
icon->paint(p, 0, 0, check->width()); icon->paint(p, emoji, 0, check->width());
}, _view->lifetime()); }
} }, _view->lifetime());
} break; } break;
case BadgeType::Scam: case BadgeType::Scam:
case BadgeType::Fake: { case BadgeType::Fake: {
@ -174,7 +184,7 @@ void Badge::setContent(Content content) {
} break; } break;
} }
if (_content.badge != BadgeType::Premium || !_premiumClickCallback) { if (!HasPremiumClick(_content) || !_premiumClickCallback) {
_view->setAttribute(Qt::WA_TransparentForMouseEvents); _view->setAttribute(Qt::WA_TransparentForMouseEvents);
} else { } else {
_view->setClickedCallback(_premiumClickCallback); _view->setClickedCallback(_premiumClickCallback);
@ -185,7 +195,7 @@ void Badge::setContent(Content content) {
void Badge::setPremiumClickCallback(Fn<void()> callback) { void Badge::setPremiumClickCallback(Fn<void()> callback) {
_premiumClickCallback = std::move(callback); _premiumClickCallback = std::move(callback);
if (_view && _content.badge == BadgeType::Premium) { if (_view && HasPremiumClick(_content)) {
if (!_premiumClickCallback) { if (!_premiumClickCallback) {
_view->setAttribute(Qt::WA_TransparentForMouseEvents); _view->setAttribute(Qt::WA_TransparentForMouseEvents);
} else { } else {

View file

@ -128,98 +128,144 @@ PeerBadge::PeerBadge() = default;
PeerBadge::~PeerBadge() = default; PeerBadge::~PeerBadge() = default;
int PeerBadge::drawGetWidth( int PeerBadge::drawGetWidth(Painter &p, Descriptor &&descriptor) {
Painter &p,
QRect rectForName,
int nameWidth,
int outerWidth,
const Descriptor &descriptor) {
Expects(descriptor.customEmojiRepaint != nullptr); Expects(descriptor.customEmojiRepaint != nullptr);
const auto peer = descriptor.peer; const auto peer = descriptor.peer;
if (descriptor.scam && (peer->isScam() || peer->isFake())) { if (descriptor.scam && (peer->isScam() || peer->isFake())) {
const auto phrase = peer->isScam() return drawScamOrFake(p, descriptor);
? tr::lng_scam_badge(tr::now) }
: tr::lng_fake_badge(tr::now); const auto verifyCheck = descriptor.verified && peer->isVerified();
const auto phraseWidth = st::dialogsScamFont->width(phrase); const auto premiumMark = descriptor.premium
const auto width = st::dialogsScamPadding.left() && peer->session().premiumBadgesShown();
+ phraseWidth const auto emojiStatus = premiumMark
+ st::dialogsScamPadding.right();
const auto height = st::dialogsScamPadding.top()
+ st::dialogsScamFont->height
+ st::dialogsScamPadding.bottom();
const auto rect = QRect(
(rectForName.x()
+ qMin(
nameWidth + st::dialogsScamSkip,
rectForName.width() - width)),
rectForName.y() + (rectForName.height() - height) / 2,
width,
height);
DrawScamFakeBadge(
p,
rect,
outerWidth,
*descriptor.scam,
phrase,
phraseWidth);
return st::dialogsScamSkip + width;
} else if (descriptor.premium
&& peer->emojiStatusId() && peer->emojiStatusId()
&& (peer->isPremium() || peer->isChannel()) && (peer->isPremium() || peer->isChannel());
&& peer->session().premiumBadgesShown()) { const auto premiumStar = premiumMark
const auto id = peer->emojiStatusId(); && !emojiStatus
const auto iconw = descriptor.premium->width(); && peer->isPremium();
const auto iconx = rectForName.x()
+ qMin(nameWidth, rectForName.width() - iconw); const auto paintVerify = verifyCheck
const auto icony = rectForName.y(); && (descriptor.prioritizeVerification
if (!_emojiStatus) { || descriptor.bothVerifyAndStatus
_emojiStatus = std::make_unique<EmojiStatus>(); || !emojiStatus);
const auto size = st::emojiSize; const auto paintEmoji = emojiStatus
const auto emoji = Ui::Text::AdjustCustomEmojiSize(size); && (!paintVerify || descriptor.bothVerifyAndStatus);
_emojiStatus->skip = (size - emoji) / 2; const auto paintStar = premiumStar && !paintVerify;
auto result = 0;
if (paintEmoji) {
auto &rectForName = descriptor.rectForName;
const auto verifyWidth = descriptor.verified->width();
if (paintVerify) {
rectForName.setWidth(rectForName.width() - verifyWidth);
} }
if (_emojiStatus->id != id) { result += drawPremiumEmojiStatus(p, descriptor);
using namespace Ui::Text; if (!paintVerify) {
auto &manager = peer->session().data().customEmojiManager(); return result;
_emojiStatus->id = id;
_emojiStatus->emoji = std::make_unique<LimitedLoopsEmoji>(
manager.create(
id,
descriptor.customEmojiRepaint),
kPlayStatusLimit);
} }
_emojiStatus->emoji->paint(p, { rectForName.setWidth(rectForName.width() + verifyWidth);
.textColor = (*descriptor.premiumFg)->c, descriptor.nameWidth += result;
.now = descriptor.now, }
.position = QPoint( if (paintVerify) {
iconx - 2 * _emojiStatus->skip, result += drawVerifyCheck(p, descriptor);
icony + _emojiStatus->skip), return result;
.paused = descriptor.paused || On(PowerSaving::kEmojiStatus), } else if (paintStar) {
}); return drawPremiumStar(p, descriptor);
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()) {
const auto iconw = descriptor.premium->width();
const auto iconx = rectForName.x()
+ qMin(nameWidth, rectForName.width() - iconw);
const auto icony = rectForName.y();
_emojiStatus = nullptr;
descriptor.premium->paint(p, iconx, icony, outerWidth);
return iconw;
} }
return 0; 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<EmojiStatus>();
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<LimitedLoopsEmoji>(
manager.create(
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;
}
void PeerBadge::unload() { void PeerBadge::unload() {
_emojiStatus = nullptr; _emojiStatus = nullptr;
} }

View file

@ -52,20 +52,20 @@ public:
struct Descriptor { struct Descriptor {
not_null<PeerData*> peer; not_null<PeerData*> peer;
QRect rectForName;
int nameWidth = 0;
int outerWidth = 0;
const style::icon *verified = nullptr; const style::icon *verified = nullptr;
const style::icon *premium = nullptr; const style::icon *premium = nullptr;
const style::color *scam = nullptr; const style::color *scam = nullptr;
const style::color *premiumFg = nullptr; const style::color *premiumFg = nullptr;
Fn<void()> customEmojiRepaint; Fn<void()> customEmojiRepaint;
crl::time now = 0; crl::time now = 0;
bool prioritizeVerification = false;
bool bothVerifyAndStatus = false;
bool paused = false; bool paused = false;
}; };
int drawGetWidth( int drawGetWidth(Painter &p, Descriptor &&descriptor);
Painter &p,
QRect rectForName,
int nameWidth,
int outerWidth,
const Descriptor &descriptor);
void unload(); void unload();
[[nodiscard]] bool ready(const BotVerifyDetails *details) const; [[nodiscard]] bool ready(const BotVerifyDetails *details) const;
@ -84,6 +84,11 @@ private:
struct EmojiStatus; struct EmojiStatus;
struct BotVerifiedData; struct BotVerifiedData;
int drawScamOrFake(Painter &p, const Descriptor &descriptor);
int drawVerifyCheck(Painter &p, const Descriptor &descriptor);
int drawPremiumEmojiStatus(Painter &p, const Descriptor &descriptor);
int drawPremiumStar(Painter &p, const Descriptor &descriptor);
std::unique_ptr<EmojiStatus> _emojiStatus; std::unique_ptr<EmojiStatus> _emojiStatus;
mutable std::unique_ptr<BotVerifiedData> _botVerifiedData; mutable std::unique_ptr<BotVerifiedData> _botVerifiedData;