Emoji status selector in MainMenu/Settings.

This commit is contained in:
John Preston 2022-08-11 20:22:21 +03:00
parent 64bd4f0926
commit bd089f20a8
18 changed files with 504 additions and 381 deletions

View file

@ -653,27 +653,45 @@ void PeerListRow::invalidatePixmapsCache() {
}
}
int PeerListRow::nameIconWidth() const {
return special()
? 0
: _peer->isVerified()
? st::dialogsVerifiedIcon.width()
: (_peer->isPremium() && !_peer->isSelf())
? st::dialogsPremiumIcon.width()
: 0;
}
void PeerListRow::paintNameIcon(
int PeerListRow::paintNameIconGetWidth(
Painter &p,
int x,
int y,
Fn<void()> repaint,
crl::time now,
int nameLeft,
int nameTop,
int nameWidth,
int availableWidth,
int outerWidth,
bool selected) {
if (_peer->isVerified()) {
st::dialogsVerifiedIcon.paint(p, x, y, outerWidth);
} else if (_peer->isPremium() && !_peer->isSelf()) {
st::dialogsPremiumIcon.paint(p, x, y, outerWidth);
if (special()
|| _isSavedMessagesChat
|| _isRepliesMessagesChat
|| !_peer->isUser()) {
return 0;
}
return _bagde.drawGetWidth(
p,
QRect(
nameLeft,
nameTop,
availableWidth,
st::msgNameStyle.font->height),
nameWidth,
outerWidth,
{
.peer = _peer,
.verified = &(selected
? st::dialogsVerifiedIconOver
: st::dialogsVerifiedIcon),
.premium = &(selected
? st::dialogsPremiumIconOver
: st::dialogsPremiumIcon),
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
.preview = st::windowBgOver->c,
.customEmojiRepaint = repaint,
.now = now,
.paused = false,
});
}
void PeerListRow::paintStatusText(
@ -829,7 +847,7 @@ PeerListContent::PeerListContent(
using UpdateFlag = Data::PeerUpdate::Flag;
_controller->session().changes().peerUpdates(
UpdateFlag::Name | UpdateFlag::Photo
UpdateFlag::Name | UpdateFlag::Photo | UpdateFlag::EmojiStatus
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
if (update.flags & UpdateFlag::Name) {
handleNameChanged(update.peer);
@ -1564,15 +1582,16 @@ crl::time PeerListContent::paintRow(
- skipRight;
}
auto statusw = namew;
if (auto iconWidth = row->nameIconWidth()) {
namew -= iconWidth;
row->paintNameIcon(
p,
namex + qMin(name.maxWidth(), namew),
_st.item.namePosition.y(),
width(),
selected);
}
namew -= row->paintNameIconGetWidth(
p,
[=] { updateRow(row); },
now,
namex,
_st.item.namePosition.y(),
name.maxWidth(),
namew,
width(),
selected);
auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio();
p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameCheckedRatio));
name.drawLeftElided(p, namex, _st.item.namePosition.y(), namew, width());

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h"
#include "ui/empty_userpic.h"
#include "ui/unread_badge.h"
#include "boxes/abstract_box.h"
#include "mtproto/sender.h"
#include "data/data_cloud_file.h"
@ -94,11 +95,14 @@ public:
void clearCustomStatus();
// Box interface.
virtual int nameIconWidth() const;
virtual void paintNameIcon(
virtual int paintNameIconGetWidth(
Painter &p,
int x,
int y,
Fn<void()> repaint,
crl::time now,
int nameLeft,
int nameTop,
int nameWidth,
int availableWidth,
int outerWidth,
bool selected);
@ -258,6 +262,7 @@ private:
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
Ui::Text::String _name;
Ui::Text::String _status;
Ui::PeerBadge _bagde;
StatusType _statusType = StatusType::Online;
crl::time _statusValidTill = 0;
base::flat_set<QChar> _nameFirstLetters;

View file

@ -1932,18 +1932,6 @@ auto ParticipantsBoxController::computeType(
? Rights::Admin
: Rights::Normal;
result.adminRank = user ? _additional.adminRank(user) : QString();
using Badge = Info::Profile::Badge;
result.badge = !user
? Badge::None
: user->isScam()
? Badge::Scam
: user->isFake()
? Badge::Fake
: user->isVerified()
? Badge::Verified
: (user->isPremium() && participant->session().premiumBadgesShown())
? Badge::Premium
: Badge::None;
return result;
}
@ -1952,7 +1940,8 @@ void ParticipantsBoxController::recomputeTypeFor(
if (_role != Role::Profile) {
return;
}
if (const auto row = delegate()->peerListFindRow(participant->id.value)) {
const auto row = delegate()->peerListFindRow(participant->id.value);
if (row) {
static_cast<Row*>(row)->setType(computeType(participant));
}
}

View file

@ -108,7 +108,16 @@ public:
Fn<void()> updateCallback) override;
void rightActionStopLastRipple() override;
int nameIconWidth() const override {
int paintNameIconGetWidth(
Painter &p,
Fn<void()> repaint,
crl::time now,
int nameLeft,
int nameTop,
int nameWidth,
int availableWidth,
int outerWidth,
bool selected) override {
return 0;
}
QSize rightActionSize() const override {

View file

@ -227,7 +227,11 @@ void TabbedPanel::paintEvent(QPaintEvent *e) {
}
void TabbedPanel::moveHorizontally() {
const auto right = std::max(parentWidget()->width() - _right, 0);
const auto padding = innerPadding();
const auto width = innerRect().width() + padding.left() + padding.right();
const auto right = std::max(
parentWidget()->width() - std::max(_right, width),
0);
moveToRight(right, y());
updateContentHeight();
}

View file

@ -20,6 +20,13 @@ InfoToggle {
rippleAreaPadding: pixels;
}
InfoPeerBadge {
verified: icon;
premium: icon;
position: point;
sizeTag: int;
}
infoMediaHeaderFg: windowFg;
infoToggleCheckbox: Checkbox(defaultCheckbox) {
@ -306,6 +313,13 @@ infoVerifiedCheck: icon {
};
infoPremiumStar: icon {{ "profile_premium", profileVerifiedCheckBg }};
infoPeerBadge: InfoPeerBadge {
verified: infoVerifiedCheck;
premium: infoPremiumStar;
position: infoVerifiedCheckPosition;
sizeTag: 1; // Large
}
infoIconFg: windowBoldFg;
infoProfileSkip: 7px;

View file

@ -81,6 +81,217 @@ auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) {
} // namespace
BadgeView::BadgeView(
not_null<QWidget*> parent,
const style::InfoPeerBadge &st,
not_null<PeerData*> peer,
Fn<bool()> animationPaused,
base::flags<Badge> allowed)
: _parent(parent)
, _st(st)
, _peer(peer)
, _allowed(allowed)
, _animationPaused(std::move(animationPaused)) {
rpl::combine(
BadgeValue(peer),
EmojiStatusIdValue(peer)
) | rpl::start_with_next([=](Badge badge, DocumentId emojiStatusId) {
setBadge(badge, emojiStatusId);
}, _lifetime);
}
Ui::RpWidget *BadgeView::widget() const {
return _view.data();
}
void BadgeView::setBadge(Badge badge, DocumentId emojiStatusId) {
if ((!_peer->session().premiumBadgesShown() && badge == Badge::Premium)
|| !(_allowed & badge)) {
badge = Badge::None;
}
if (!(_allowed & badge)) {
badge = Badge::None;
}
if (badge != Badge::Premium) {
emojiStatusId = 0;
}
if (_badge == badge && _emojiStatusId == emojiStatusId) {
return;
}
_badge = badge;
_emojiStatusId = emojiStatusId;
_emojiStatus = nullptr;
_view.destroy();
if (_badge == Badge::None) {
_updated.fire({});
return;
}
_view.create(_parent);
_view->show();
switch (_badge) {
case Badge::Verified:
case Badge::Premium: {
if (_emojiStatusId) {
using SizeTag = Data::CustomEmojiManager::SizeTag;
const auto tag = (_st.sizeTag == 2)
? SizeTag::Isolated
: (_st.sizeTag == 1)
? SizeTag::Large
: SizeTag::Normal;
_emojiStatus = _peer->owner().customEmojiManager().create(
_emojiStatusId,
[raw = _view.data()]{ raw->update(); },
tag);
const auto emoji = Data::FrameSizeFromTag(tag)
/ style::DevicePixelRatio();
_view->resize(emoji, emoji);
_view->paintRequest(
) | rpl::start_with_next([=, check = _view.data()]{
Painter p(check);
_emojiStatus->paint(
p,
0,
0,
crl::now(),
st::windowBgOver->c,
_animationPaused && _animationPaused());
}, _view->lifetime());
} else {
const auto icon = (_badge == Badge::Verified)
? &_st.verified
: &_st.premium;
_view->resize(icon->size());
_view->paintRequest(
) | rpl::start_with_next([=, check = _view.data()]{
Painter p(check);
icon->paint(p, 0, 0, check->width());
}, _view->lifetime());
}
} break;
case Badge::Scam:
case Badge::Fake: {
const auto fake = (_badge == Badge::Fake);
const auto size = Ui::ScamBadgeSize(fake);
const auto skip = st::infoVerifiedCheckPosition.x();
_view->resize(
size.width() + 2 * skip,
size.height() + 2 * skip);
_view->paintRequest(
) | rpl::start_with_next([=, badge = _view.data()]{
Painter p(badge);
Ui::DrawScamBadge(
fake,
p,
badge->rect().marginsRemoved({ skip, skip, skip, skip }),
badge->width(),
st::attentionButtonFg);
}, _view->lifetime());
} break;
}
if (_badge != Badge::Premium || !_premiumClickCallback) {
_view->setAttribute(Qt::WA_TransparentForMouseEvents);
} else {
_view->setClickedCallback(_premiumClickCallback);
}
_updated.fire({});
}
void BadgeView::setPremiumClickCallback(Fn<void()> callback) {
_premiumClickCallback = std::move(callback);
if (_view && _badge == Badge::Premium) {
if (!_premiumClickCallback) {
_view->setAttribute(Qt::WA_TransparentForMouseEvents);
} else {
_view->setAttribute(Qt::WA_TransparentForMouseEvents, false);
_view->setClickedCallback(_premiumClickCallback);
}
}
}
rpl::producer<> BadgeView::updated() const {
return _updated.events();
}
void BadgeView::move(int left, int top, int bottom) {
if (!_view) {
return;
}
const auto star = !_emojiStatus
&& (_badge == Badge::Premium || _badge == Badge::Verified);
const auto fake = !_emojiStatus && !star;
const auto skip = fake ? 0 : _st.position.x();
const auto badgeLeft = left + skip;
const auto badgeTop = top
+ (star
? st::infoVerifiedCheckPosition.y()
: (bottom - top - _view->height()) / 2);
_view->moveToLeft(badgeLeft, badgeTop);
}
void EmojiStatusPanel::show(
not_null<Window::SessionController*> controller,
not_null<QWidget*> button) {
if (!_panel) {
create(controller);
using namespace rpl::mappers;
_panel->shownValue(
) | rpl::filter(
!_1
) | rpl::start_with_next([=] {
button->removeEventFilter(_panel.get());
}, _panel->lifetime());
}
const auto parent = _panel->parentWidget();
const auto global = button->mapToGlobal({ 0, 0 });
const auto local = parent->mapFromGlobal(global);
_panel->moveTopRight(
local.y() + button->height(),
local.x() + button->width() * 3);
_panel->toggleAnimated();
button->installEventFilter(_panel.get());
}
void EmojiStatusPanel::create(
not_null<Window::SessionController*> controller) {
using Selector = ChatHelpers::TabbedSelector;
_panel = base::make_unique_q<ChatHelpers::TabbedPanel>(
controller->window().widget()->bodyWidget(),
controller,
object_ptr<Selector>(
nullptr,
controller,
Window::GifPauseReason::Layer,
ChatHelpers::TabbedSelector::Mode::EmojiStatus));
_panel->setDropDown(true);
_panel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
_panel->hide();
_panel->selector()->setAllowEmojiWithoutPremium(false);
auto statusChosen = _panel->selector()->customEmojiChosen(
) | rpl::map([=](Selector::FileChosen data) {
return data.document->id;
});
rpl::merge(
std::move(statusChosen),
_panel->selector()->emojiChosen() | rpl::map_to(DocumentId())
) | rpl::start_with_next([=](DocumentId id) {
controller->session().user()->setEmojiStatus(id);
controller->session().api().request(MTPaccount_UpdateEmojiStatus(
id ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusEmpty()
)).send();
_panel->hideAnimated();
}, _panel->lifetime());
_panel->selector()->showPromoForPremiumEmoji();
}
Cover::Cover(
QWidget *parent,
not_null<PeerData*> peer,
@ -104,6 +315,14 @@ Cover::Cover(
+ st::infoProfilePhotoBottom)
, _controller(controller)
, _peer(peer)
, _badge(
this,
st::infoPeerBadge,
peer,
[=] {
return controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
})
, _userpic(
this,
controller,
@ -126,6 +345,19 @@ Cover::Cover(
_status->setAttribute(Qt::WA_TransparentForMouseEvents);
}
_badge.setPremiumClickCallback([=] {
if (_peer->isSelf()) {
_emojiStatusPanel.show(_controller, _badge.widget());
} else {
::Settings::ShowPremium(
_controller,
u"profile__%1"_q.arg(peerToUser(_peer->id).bare));
}
});
_badge.updated() | rpl::start_with_next([=] {
refreshNameGeometry(width());
}, _name->lifetime());
initViewers(std::move(title));
setupChildGeometry();
@ -184,12 +416,6 @@ void Cover::initViewers(rpl::producer<QString> title) {
} else if (_peer->isSelf()) {
refreshUploadPhotoOverlay();
}
rpl::combine(
BadgeValue(_peer),
EmojiStatusIdValue(_peer)
) | rpl::start_with_next([=](Badge badge, DocumentId emojiStatusId) {
setBadge(badge, emojiStatusId);
}, lifetime());
}
void Cover::refreshUploadPhotoOverlay() {
@ -203,151 +429,6 @@ void Cover::refreshUploadPhotoOverlay() {
}());
}
void Cover::setBadge(Badge badge, DocumentId emojiStatusId) {
if (!_peer->session().premiumBadgesShown() && badge == Badge::Premium) {
badge = Badge::None;
}
if (badge != Badge::Premium) {
emojiStatusId = 0;
}
if (_badge == badge && _emojiStatusId == emojiStatusId) {
return;
}
_badge = badge;
_emojiStatusId = emojiStatusId;
_emojiStatus = nullptr;
_badgeView.destroy();
switch (_badge) {
case Badge::Verified:
case Badge::Premium: {
_badgeView.create(this);
_badgeView->show();
if (_emojiStatusId) {
auto &owner = _controller->session().data();
_emojiStatus = owner.customEmojiManager().create(
_emojiStatusId,
[raw = _badgeView.data()]{ raw->update(); },
Data::CustomEmojiManager::SizeTag::Large);
const auto size = Ui::Emoji::GetSizeLarge()
/ style::DevicePixelRatio();
const auto emoji = Ui::Text::AdjustCustomEmojiSize(size);
_badgeView->resize(emoji, emoji);
_badgeView->paintRequest(
) | rpl::start_with_next([=, check = _badgeView.data()]{
Painter p(check);
_emojiStatus->paint(
p,
0,
0,
crl::now(),
st::windowBgOver->c,
_controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer));
}, _badgeView->lifetime());
} else {
const auto icon = (_badge == Badge::Verified)
? &st::infoVerifiedCheck
: &st::infoPremiumStar;
_badgeView->resize(icon->size());
_badgeView->paintRequest(
) | rpl::start_with_next([=, check = _badgeView.data()]{
Painter p(check);
icon->paint(p, 0, 0, check->width());
}, _badgeView->lifetime());
}
} break;
case Badge::Scam:
case Badge::Fake: {
const auto fake = (_badge == Badge::Fake);
const auto size = Ui::ScamBadgeSize(fake);
const auto skip = st::infoVerifiedCheckPosition.x();
_badgeView.create(this);
_badgeView->show();
_badgeView->resize(
size.width() + 2 * skip,
size.height() + 2 * skip);
_badgeView->paintRequest(
) | rpl::start_with_next([=, badge = _badgeView.data()]{
Painter p(badge);
Ui::DrawScamBadge(
fake,
p,
badge->rect().marginsRemoved({ skip, skip, skip, skip }),
badge->width(),
st::attentionButtonFg);
}, _badgeView->lifetime());
} break;
}
if (_badge == Badge::Premium) {
const auto userId = peerToUser(_peer->id).bare;
_badgeView->setClickedCallback([=] {
if (_peer->isSelf()) {
showEmojiStatusSelector();
} else {
::Settings::ShowPremium(
_controller,
u"profile__%1"_q.arg(userId));
}
});
} else {
_badgeView->setAttribute(Qt::WA_TransparentForMouseEvents);
}
refreshNameGeometry(width());
}
void Cover::showEmojiStatusSelector() {
Expects(_badgeView != nullptr);
if (!_emojiStatusPanel) {
createEmojiStatusSelector();
}
const auto parent = _emojiStatusPanel->parentWidget();
const auto global = _badgeView->mapToGlobal({ 0, 0 });
const auto local = parent->mapFromGlobal(global);
_emojiStatusPanel->moveTopRight(
local.y() + _badgeView->height(),
local.x() + _badgeView->width() * 3);
_emojiStatusPanel->toggleAnimated();
}
void Cover::createEmojiStatusSelector() {
const auto set = [=](DocumentId id) {
_controller->session().user()->setEmojiStatus(id);
_controller->session().api().request(MTPaccount_UpdateEmojiStatus(
id ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusEmpty()
)).send();
_emojiStatusPanel->hideAnimated();
};
const auto container = _controller->window().widget()->bodyWidget();
using Selector = ChatHelpers::TabbedSelector;
_emojiStatusPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
container,
_controller,
object_ptr<Selector>(
nullptr,
_controller,
Window::GifPauseReason::Layer,
ChatHelpers::TabbedSelector::Mode::EmojiStatus));
_emojiStatusPanel->setDropDown(true);
_emojiStatusPanel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
_emojiStatusPanel->hide();
_emojiStatusPanel->selector()->setAllowEmojiWithoutPremium(false);
_emojiStatusPanel->selector()->emojiChosen(
) | rpl::start_with_next([=] {
set(0);
}, _emojiStatusPanel->lifetime());
_emojiStatusPanel->selector()->customEmojiChosen(
) | rpl::start_with_next([=](Selector::FileChosen data) {
set(data.document->id);
}, _emojiStatusPanel->lifetime());
_emojiStatusPanel->selector()->showPromoForPremiumEmoji();
}
void Cover::refreshStatusText() {
auto hasMembersLink = [&] {
if (auto megagroup = _peer->asMegagroup()) {
@ -406,23 +487,15 @@ void Cover::refreshNameGeometry(int newWidth) {
auto nameWidth = newWidth
- nameLeft
- st::infoProfileNameRight;
if (_badgeView) {
nameWidth -= st::infoVerifiedCheckPosition.x() + _badgeView->width();
if (const auto width = _badge.widget() ? _badge.widget()->width() : 0) {
nameWidth -= st::infoVerifiedCheckPosition.x() + width;
}
_name->resizeToNaturalWidth(nameWidth);
_name->moveToLeft(nameLeft, nameTop, newWidth);
if (_badgeView) {
const auto star = !_emojiStatus
&& (_badge == Badge::Premium || _badge == Badge::Verified);
const auto fake = !_emojiStatus && !star;
const auto skip = fake ? 0 : st::infoVerifiedCheckPosition.x();
const auto badgeLeft = nameLeft + _name->width() + skip;
const auto badgeTop = nameTop
+ (star
? st::infoVerifiedCheckPosition.y()
: (_name->height() - _badgeView->height()) / 2);
_badgeView->moveToLeft(badgeLeft, badgeTop, newWidth);
}
const auto badgeLeft = nameLeft + _name->width();
const auto badgeTop = nameTop;
const auto badgeBottom = nameTop + _name->height();
_badge.move(badgeLeft, badgeTop, badgeBottom);
}
void Cover::refreshStatusGeometry(int newWidth) {

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/checkbox.h"
#include "base/timer.h"
#include "base/flags.h"
namespace Window {
class SessionController;
@ -17,6 +18,7 @@ class SessionController;
namespace style {
struct InfoToggle;
struct InfoPeerBadge;
} // namespace style
namespace ChatHelpers {
@ -39,9 +41,62 @@ class Section;
namespace Info {
namespace Profile {
enum class Badge;
enum class Badge {
None = 0x00,
Verified = 0x01,
Premium = 0x02,
Scam = 0x04,
Fake = 0x08,
};
inline constexpr bool is_flag_type(Badge) { return true; }
class Cover : public Ui::FixedHeightWidget {
class BadgeView final {
public:
BadgeView(
not_null<QWidget*> parent,
const style::InfoPeerBadge &st,
not_null<PeerData*> peer,
Fn<bool()> animationPaused,
base::flags<Badge> allowed = base::flags<Badge>::from_raw(-1));
[[nodiscard]] Ui::RpWidget *widget() const;
void setPremiumClickCallback(Fn<void()> callback);
[[nodiscrd]] rpl::producer<> updated() const;
void move(int left, int top, int bottom);
private:
void setBadge(Badge badge, DocumentId emojiStatusId);
const not_null<QWidget*> _parent;
const style::InfoPeerBadge &_st;
const not_null<PeerData*> _peer;
DocumentId _emojiStatusId = 0;
std::unique_ptr<Ui::Text::CustomEmoji> _emojiStatus;
base::flags<Badge> _allowed;
Badge _badge = Badge();
Fn<void()> _premiumClickCallback;
Fn<bool()> _animationPaused;
object_ptr<Ui::AbstractButton> _view = { nullptr };
rpl::event_stream<> _updated;
rpl::lifetime _lifetime;
};
class EmojiStatusPanel final {
public:
void show(
not_null<Window::SessionController*> controller,
not_null<QWidget*> button);
private:
void create(not_null<Window::SessionController*> controller);
base::unique_qptr<ChatHelpers::TabbedPanel> _panel;
};
class Cover final : public Ui::FixedHeightWidget {
public:
Cover(
QWidget *parent,
@ -68,21 +123,15 @@ private:
void refreshNameGeometry(int newWidth);
void refreshStatusGeometry(int newWidth);
void refreshUploadPhotoOverlay();
void setBadge(Badge badge, DocumentId emojiStatusId);
void createEmojiStatusSelector();
void showEmojiStatusSelector();
const not_null<Window::SessionController*> _controller;
const not_null<PeerData*> _peer;
DocumentId _emojiStatusId = 0;
std::unique_ptr<Ui::Text::CustomEmoji> _emojiStatus;
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiStatusPanel;
BadgeView _badge;
EmojiStatusPanel _emojiStatusPanel;
int _onlineCount = 0;
Badge _badge = Badge();
object_ptr<Ui::UserpicButton> _userpic;
object_ptr<Ui::FlatLabel> _name = { nullptr };
object_ptr<Ui::AbstractButton> _badgeView = { nullptr };
object_ptr<Ui::FlatLabel> _status = { nullptr };
//object_ptr<CoverDropArea> _dropArea = { nullptr };
base::Timer _refreshStatusTimer;

View file

@ -30,11 +30,6 @@ MemberListRow::MemberListRow(
void MemberListRow::setType(Type type) {
_type = type;
_fakeScamSize = (_type.badge == Badge::Fake)
? Ui::ScamBadgeSize(true)
: (_type.badge == Badge::Scam)
? Ui::ScamBadgeSize(false)
: QSize();
PeerListRowWithLink::setActionLink(!_type.adminRank.isEmpty()
? _type.adminRank
: (_type.rights == Rights::Creator)
@ -57,55 +52,10 @@ QMargins MemberListRow::rightActionMargins() const {
0);
}
int MemberListRow::nameIconWidth() const {
switch (_type.badge) {
case Badge::None: return 0;
case Badge::Verified: return st::dialogsVerifiedIcon.width();
case Badge::Premium: return st::dialogsPremiumIcon.width();
case Badge::Scam:
case Badge::Fake:
return st::dialogsScamSkip + _fakeScamSize.width();
}
return 0;
}
not_null<UserData*> MemberListRow::user() const {
return peer()->asUser();
}
void MemberListRow::paintNameIcon(
Painter &p,
int x,
int y,
int outerWidth,
bool selected) {
switch (_type.badge) {
case Badge::None: return;
case Badge::Verified:
(selected
? st::dialogsVerifiedIconOver
: st::dialogsVerifiedIcon).paint(p, x, y, outerWidth);
break;
case Badge::Premium:
(selected
? st::dialogsPremiumIconOver
: st::dialogsPremiumIcon).paint(p, x, y, outerWidth);
break;
case Badge::Scam:
case Badge::Fake:
return Ui::DrawScamBadge(
(_type.badge == Badge::Fake),
p,
QRect(
x + st::dialogsScamSkip,
y + (st::normalFont->height - _fakeScamSize.height()) / 2,
_fakeScamSize.width(),
_fakeScamSize.height()),
outerWidth,
(selected ? st::dialogsScamFgOver : st::dialogsScamFg));
}
}
void MemberListRow::refreshStatus() {
if (user()->isBot()) {
const auto seesAllMessages = (user()->botInfo->readsAllHistory

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "boxes/peer_list_controllers.h"
#include "ui/unread_badge.h"
namespace Window {
class SessionNavigation;
@ -16,8 +17,6 @@ class SessionNavigation;
namespace Info {
namespace Profile {
enum class Badge;
class MemberListRow final : public PeerListRowWithLink {
public:
enum class Rights {
@ -26,7 +25,6 @@ public:
Creator,
};
struct Type {
Badge badge;
Rights rights;
QString adminRank;
};
@ -36,20 +34,12 @@ public:
void setType(Type type);
bool rightActionDisabled() const override;
QMargins rightActionMargins() const override;
int nameIconWidth() const override;
void paintNameIcon(
Painter &p,
int x,
int y,
int outerWidth,
bool selected) override;
void refreshStatus() override;
not_null<UserData*> user() const;
private:
Type _type;
QSize _fakeScamSize;
};

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "info/profile/info_profile_values.h"
#include "info/profile/info_profile_cover.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "countries/countries_instance.h"

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/flags.h"
#include <rpl/producer.h>
#include <rpl/map.h>
@ -90,13 +92,7 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
[[nodiscard]] rpl::producer<int> AllowedReactionsCountValue(
not_null<PeerData*> peer);
enum class Badge {
None,
Verified,
Premium,
Scam,
Fake,
};
enum class Badge;
[[nodiscard]] rpl::producer<Badge> BadgeValue(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<DocumentId> EmojiStatusIdValue(
not_null<PeerData*> peer);

View file

@ -184,6 +184,10 @@ settingsInfoPhotoTop: 0px;
settingsInfoPhotoSkip: 7px;
settingsInfoNameSkip: -1px;
settingsInfoUploadLeft: 6px;
settingsInfoPeerBadge: InfoPeerBadge {
verified: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBg }};
sizeTag: 0; // Normal
}
settingsBio: InputField(defaultInputField) {
textBg: transparent;

View file

@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_premium_limits.h"
#include "dialogs/ui/dialogs_layout.h"
#include "info/profile/info_profile_values.h"
#include "info/profile/info_profile_cover.h"
#include "lang/lang_keys.h"
#include "main/main_account.h"
#include "main/main_session.h"
@ -71,7 +72,8 @@ public:
not_null<Ui::SettingsButton*> button,
not_null<Main::Session*> session,
rpl::producer<QString> &&text,
bool hasUnread);
bool hasUnread,
Fn<bool()> animationPaused);
private:
rpl::variable<QString> _text;
@ -79,7 +81,7 @@ private:
rpl::event_stream<int> _premiumWidth;
QPointer<Ui::RpWidget> _unread;
QPointer<Ui::RpWidget> _premium;
Info::Profile::BadgeView _badge;
};
@ -88,9 +90,16 @@ ComposedBadge::ComposedBadge(
not_null<Ui::SettingsButton*> button,
not_null<Main::Session*> session,
rpl::producer<QString> &&text,
bool hasUnread)
bool hasUnread,
Fn<bool()> animationPaused)
: Ui::RpWidget(parent)
, _text(std::move(text)) {
, _text(std::move(text))
, _badge(
this,
st::settingsInfoPeerBadge,
session->user(),
std::move(animationPaused),
Info::Profile::Badge::Premium) {
if (hasUnread) {
_unread = CreateUnread(this, rpl::single(
rpl::empty
@ -111,35 +120,19 @@ ComposedBadge::ComposedBadge(
}) | rpl::start_to_stream(_unreadWidth, _unread->lifetime());
}
Data::AmPremiumValue(
session
) | rpl::start_with_next([=](bool hasPremium) {
if (hasPremium && !_premium) {
_premium = Ui::CreateChild<Ui::RpWidget>(this);
const auto offset = st::dialogsPremiumIconOffset;
_premium->resize(
st::dialogsPremiumIcon.width() - offset.x(),
st::dialogsPremiumIcon.height() - offset.y());
_premium->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
Painter p(_premium);
st::dialogsPremiumIcon.paint(
p,
-offset.x(),
-offset.y(),
_premium->width());
}, _premium->lifetime());
_premium->widthValue(
) | rpl::start_to_stream(_premiumWidth, _premium->lifetime());
} else if (!hasPremium && _premium) {
_premium = nullptr;
_badge.updated(
) | rpl::start_with_next([=] {
if (const auto button = _badge.widget()) {
button->widthValue(
) | rpl::start_to_stream(_premiumWidth, button->lifetime());
} else {
_premiumWidth.fire(0);
}
}, lifetime());
rpl::combine(
_unreadWidth.events_starting_with(_unread ? _unread->width() : 0),
_premiumWidth.events_starting_with(_premium ? _premium->width() : 0),
_premiumWidth.events_starting_with(0),
_text.value(),
button->sizeValue()
) | rpl::start_with_next([=](
@ -151,7 +144,7 @@ ComposedBadge::ComposedBadge(
const auto skip = st.style.font->spacew;
const auto textRightPosition = st.padding.left()
+ st.style.font->width(text)
+ skip * 2;
+ skip;
const auto minWidth = unreadWidth + premiumWidth + skip;
const auto maxTextWidth = buttonSize.width()
- minWidth
@ -163,9 +156,7 @@ ComposedBadge::ComposedBadge(
buttonSize.width() - st.padding.right() - finalTextRight,
buttonSize.height());
if (_premium) {
_premium->moveToLeft(0, st.padding.top());
}
_badge.move(0, 0, buttonSize.height());
if (_unread) {
_unread->moveToRight(
0,
@ -618,7 +609,9 @@ void SetupAccountsWrap(
raw,
session,
std::move(text),
!active);
!active,
[=] { return window->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer); });
composedBadge->sizeValue(
) | rpl::start_with_next([=](const QSize &s) {
container->resize(s);
@ -986,9 +979,7 @@ not_null<Ui::RpWidget*> AddRight(
padding.right(),
(outer.height() - inner.height()) / 2,
outer.width());
padding.setRight(padding.right()
+ inner.width()
+ button->st().style.font->spacew);
padding.setRight(padding.right() + inner.width());
}
button->setPaddingOverride(padding);
button->update();

View file

@ -85,12 +85,13 @@ private:
const not_null<Window::SessionController*> _controller;
const not_null<UserData*> _user;
Info::Profile::BadgeView _badge;
Info::Profile::EmojiStatusPanel _emojiStatusPanel;
object_ptr<Ui::UserpicButton> _userpic;
object_ptr<Ui::FlatLabel> _name = { nullptr };
object_ptr<Ui::FlatLabel> _phone = { nullptr };
object_ptr<Ui::FlatLabel> _username = { nullptr };
object_ptr<Ui::RpWidget> _badge = { nullptr };
};
@ -105,6 +106,15 @@ Cover::Cover(
+ st::settingsPhotoBottom)
, _controller(controller)
, _user(user)
, _badge(
this,
st::infoPeerBadge,
user,
[=] {
return controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
},
Info::Profile::Badge::Premium)
, _userpic(
this,
controller,
@ -133,23 +143,12 @@ Cover::Cover(
_userpic->takeResultImage());
}, _userpic->lifetime());
Data::AmPremiumValue(
&controller->session()
) | rpl::start_with_next([=](bool hasPremium) {
if (hasPremium && !_badge) {
const auto icon = &st::infoPremiumStar;
_badge.create(this);
_badge->show();
_badge->resize(icon->size());
_badge->paintRequest(
) | rpl::start_with_next([icon, check = _badge.data()] {
Painter p(check);
icon->paint(p, 0, 0, check->width());
}, _badge->lifetime());
} else if (!hasPremium && _badge) {
_badge.destroy();
}
}, lifetime());
_badge.setPremiumClickCallback([=] {
_emojiStatusPanel.show(_controller, _badge.widget());
});
_badge.updated() | rpl::start_with_next([=] {
refreshNameGeometry(width());
}, _name->lifetime());
}
Cover::~Cover() = default;
@ -210,19 +209,18 @@ void Cover::initViewers() {
void Cover::refreshNameGeometry(int newWidth) {
const auto nameLeft = st::settingsNameLeft;
const auto nameTop = st::settingsNameTop;
const auto nameWidth = newWidth
auto nameWidth = newWidth
- nameLeft
- st::infoProfileNameRight
- (!_badge ? 0 : _badge->width() + st::infoVerifiedCheckPosition.x());
- st::infoProfileNameRight;
if (const auto width = _badge.widget() ? _badge.widget()->width() : 0) {
nameWidth -= st::infoVerifiedCheckPosition.x() + width;
}
_name->resizeToNaturalWidth(nameWidth);
_name->moveToLeft(nameLeft, nameTop, newWidth);
if (_badge) {
const auto &pos = st::infoVerifiedCheckPosition;
const auto badgeLeft = nameLeft + _name->width() + pos.x();
const auto badgeTop = nameTop + pos.y();
_badge->moveToLeft(badgeLeft, badgeTop, newWidth);
}
const auto badgeLeft = nameLeft + _name->width();
const auto badgeTop = nameTop;
const auto badgeBottom = nameTop + _name->height();
_badge.move(badgeLeft, badgeTop, badgeBottom);
}
void Cover::refreshPhoneGeometry(int newWidth) {

View file

@ -182,12 +182,12 @@ int PeerBadge::drawGetWidth(
}
_emojiStatus->emoji->paint(
p,
iconx - _emojiStatus->skip,
iconx - 2 * _emojiStatus->skip,
icony + _emojiStatus->skip,
descriptor.now,
descriptor.preview,
descriptor.paused);
return iconw - 2 * _emojiStatus->skip;
return iconw - 4 * _emojiStatus->skip;
}
return 0;
}

View file

@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h"
#include "settings/settings_calls.h"
#include "settings/settings_information.h"
#include "info/profile/info_profile_cover.h"
#include "base/qt_signal_producer.h"
#include "boxes/about_box.h"
#include "ui/boxes/confirm_box.h"
@ -120,7 +121,10 @@ public:
explicit ToggleAccountsButton(QWidget *parent);
[[nodiscard]] int rightSkip() const {
return _rightSkip;
return _rightSkip.current();
}
[[nodiscard]] rpl::producer<int> rightSkipValue() const {
return _rightSkip.value();
}
private:
@ -130,7 +134,7 @@ private:
void validateUnreadBadge();
[[nodiscard]] QString computeUnreadBadge() const;
int _rightSkip = 0;
rpl::variable<int> _rightSkip = 0;
Ui::Animations::Simple _toggledAnimation;
bool _toggled = false;
@ -258,12 +262,13 @@ void MainMenu::ToggleAccountsButton::validateUnreadBadge() {
}
_unreadBadge = computeUnreadBadge();
_rightSkip = base;
auto skip = base;
if (!_unreadBadge.isEmpty()) {
const auto st = Settings::Badge::Style();
_rightSkip += 2 * st::mainMenuToggleSize
skip += 2 * st::mainMenuToggleSize
+ Dialogs::Ui::CountUnreadBadgeSize(_unreadBadge, st).width();
}
_rightSkip = skip;
}
QString MainMenu::ToggleAccountsButton::computeUnreadBadge() const {
@ -331,6 +336,13 @@ MainMenu::MainMenu(
Ui::UserpicButton::Role::Custom,
st::mainMenuUserpic)
, _toggleAccounts(this)
, _badge(std::make_unique<Info::Profile::BadgeView>(
this,
st::settingsInfoPeerBadge,
controller->session().user(),
[=] { return controller->isGifPausedAtLeastFor(GifPauseReason::Layer); },
Info::Profile::Badge::Premium))
, _emojiStatusPanel(std::make_unique<Info::Profile::EmojiStatusPanel>())
, _scroll(this, st::defaultSolidScroll)
, _inner(_scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(_scroll.data())))
@ -416,6 +428,16 @@ MainMenu::MainMenu(
controller->show(Box<AboutBox>());
}));
rpl::combine(
_toggleAccounts->rightSkipValue(),
rpl::single(rpl::empty) | rpl::then(_badge->updated())
) | rpl::start_with_next([=] {
moveBadge();
}, lifetime());
_badge->setPremiumClickCallback([=] {
_emojiStatusPanel->show(_controller, _badge->widget());
});
_controller->session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
update();
@ -432,6 +454,24 @@ MainMenu::MainMenu(
initResetScaleButton();
}
MainMenu::~MainMenu() = default;
void MainMenu::moveBadge() {
if (!_badge->widget()) {
return;
}
const auto available = width()
- st::mainMenuCoverNameLeft
- _toggleAccounts->rightSkip()
- _badge->widget()->width();
const auto left = st::mainMenuCoverNameLeft
+ std::min(_name.maxWidth() + st::semiboldFont->spacew, available);
_badge->move(
left,
st::mainMenuCoverNameTop,
st::mainMenuCoverNameTop + st::msgNameStyle.font->height);
}
void MainMenu::setupArchive() {
using namespace Settings;
@ -771,30 +811,10 @@ void MainMenu::paintEvent(QPaintEvent *e) {
st::msgNameStyle,
user->name(),
Ui::NameTextOptions());
moveBadge();
}
const auto paused = _controller->isGifPausedAtLeastFor(
GifPauseReason::Layer);
const auto badgeWidth = user->emojiStatusId()
? _badge.drawGetWidth(
p,
QRect(
st::mainMenuCoverNameLeft,
st::mainMenuCoverNameTop,
widthText,
st::msgNameStyle.font->height),
_name.maxWidth(),
width(),
{
.peer = user,
.verified = nullptr,
.premium = &st::dialogsPremiumIcon,
.scam = nullptr,
.preview = st::windowBgOver->c,
.customEmojiRepaint = [=] { update(); },
.now = crl::now(),
.paused = paused,
})
: 0;
p.setFont(st::semiboldFont);
p.setPen(st::windowBoldFg);
@ -802,7 +822,10 @@ void MainMenu::paintEvent(QPaintEvent *e) {
p,
st::mainMenuCoverNameLeft,
st::mainMenuCoverNameTop,
widthText - badgeWidth,
(widthText
- (_badge->widget()
? (st::semiboldFont->spacew + _badge->widget()->width())
: 0)),
width());
p.setFont(st::mainMenuPhoneFont);
p.setPen(st::windowSubTextFg);

View file

@ -28,6 +28,11 @@ template <typename Widget>
class SlideWrap;
} // namespace Ui
namespace Info::Profile {
class BadgeView;
class EmojiStatusPanel;
} // namespace Info::Profile
namespace Main {
class Account;
} // namespace Main
@ -39,6 +44,7 @@ class SessionController;
class MainMenu final : public Ui::LayerWidget {
public:
MainMenu(QWidget *parent, not_null<SessionController*> controller);
~MainMenu();
void parentResized() override;
@ -54,6 +60,7 @@ private:
class ToggleAccountsButton;
class ResetScaleButton;
void moveBadge();
void setupUserpicButton();
void setupAccounts();
void setupAccountsToggle();
@ -67,10 +74,11 @@ private:
const not_null<SessionController*> _controller;
object_ptr<Ui::UserpicButton> _userpicButton;
Ui::PeerBadge _badge;
Ui::Text::String _name;
int _nameVersion = 0;
object_ptr<ToggleAccountsButton> _toggleAccounts;
std::unique_ptr<Info::Profile::BadgeView> _badge;
std::unique_ptr<Info::Profile::EmojiStatusPanel> _emojiStatusPanel;
object_ptr<ResetScaleButton> _resetScaleButton = { nullptr };
object_ptr<Ui::ScrollArea> _scroll;
not_null<Ui::VerticalLayout*> _inner;