From bd089f20a8a305526842a23f0af20fc6bb6b5d95 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 11 Aug 2022 20:22:21 +0300 Subject: [PATCH] Emoji status selector in MainMenu/Settings. --- Telegram/SourceFiles/boxes/peer_list_box.cpp | 73 ++-- Telegram/SourceFiles/boxes/peer_list_box.h | 13 +- .../boxes/peers/edit_participants_box.cpp | 15 +- .../calls/calls_box_controller.cpp | 11 +- .../SourceFiles/chat_helpers/tabbed_panel.cpp | 6 +- Telegram/SourceFiles/info/info.style | 14 + .../info/profile/info_profile_cover.cpp | 403 +++++++++++------- .../info/profile/info_profile_cover.h | 69 ++- .../info_profile_members_controllers.cpp | 50 --- .../info_profile_members_controllers.h | 12 +- .../info/profile/info_profile_values.cpp | 1 + .../info/profile/info_profile_values.h | 10 +- Telegram/SourceFiles/settings/settings.style | 4 + .../settings/settings_information.cpp | 61 ++- .../SourceFiles/settings/settings_main.cpp | 54 ++- Telegram/SourceFiles/ui/unread_badge.cpp | 4 +- .../SourceFiles/window/window_main_menu.cpp | 75 ++-- .../SourceFiles/window/window_main_menu.h | 10 +- 18 files changed, 504 insertions(+), 381 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index d81ba849e..e40d99cdf 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -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 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()); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 34377542b..99e64a416 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -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 repaint, + crl::time now, + int nameLeft, + int nameTop, + int nameWidth, + int availableWidth, int outerWidth, bool selected); @@ -258,6 +262,7 @@ private: std::unique_ptr _checkbox; Ui::Text::String _name; Ui::Text::String _status; + Ui::PeerBadge _bagde; StatusType _statusType = StatusType::Online; crl::time _statusValidTill = 0; base::flat_set _nameFirstLetters; diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index a94da0a59..45f2d33f2 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -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)->setType(computeType(participant)); } } diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index 2fc5122b7..c86d1e1aa 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -108,7 +108,16 @@ public: Fn updateCallback) override; void rightActionStopLastRipple() override; - int nameIconWidth() const override { + int paintNameIconGetWidth( + Painter &p, + Fn repaint, + crl::time now, + int nameLeft, + int nameTop, + int nameWidth, + int availableWidth, + int outerWidth, + bool selected) override { return 0; } QSize rightActionSize() const override { diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp index 6b26c494e..6c1800e51 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp @@ -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(); } diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 954f1f2c5..abd87c2c8 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -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; diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index b8c861171..30d052b8a 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -81,6 +81,217 @@ auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) { } // namespace +BadgeView::BadgeView( + not_null parent, + const style::InfoPeerBadge &st, + not_null peer, + Fn animationPaused, + base::flags 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 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 controller, + not_null 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 controller) { + using Selector = ChatHelpers::TabbedSelector; + _panel = base::make_unique_q( + controller->window().widget()->bodyWidget(), + controller, + object_ptr( + 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 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 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( - container, - _controller, - object_ptr( - 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) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index 3a18c0674..ea7a4e26e 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -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 parent, + const style::InfoPeerBadge &st, + not_null peer, + Fn animationPaused, + base::flags allowed = base::flags::from_raw(-1)); + + [[nodiscard]] Ui::RpWidget *widget() const; + + void setPremiumClickCallback(Fn callback); + [[nodiscrd]] rpl::producer<> updated() const; + void move(int left, int top, int bottom); + +private: + void setBadge(Badge badge, DocumentId emojiStatusId); + + const not_null _parent; + const style::InfoPeerBadge &_st; + const not_null _peer; + DocumentId _emojiStatusId = 0; + std::unique_ptr _emojiStatus; + base::flags _allowed; + Badge _badge = Badge(); + Fn _premiumClickCallback; + Fn _animationPaused; + object_ptr _view = { nullptr }; + rpl::event_stream<> _updated; + rpl::lifetime _lifetime; + +}; + +class EmojiStatusPanel final { +public: + void show( + not_null controller, + not_null button); + +private: + void create(not_null controller); + + base::unique_qptr _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 _controller; const not_null _peer; - DocumentId _emojiStatusId = 0; - std::unique_ptr _emojiStatus; - base::unique_qptr _emojiStatusPanel; + BadgeView _badge; + EmojiStatusPanel _emojiStatusPanel; int _onlineCount = 0; - Badge _badge = Badge(); object_ptr _userpic; object_ptr _name = { nullptr }; - object_ptr _badgeView = { nullptr }; object_ptr _status = { nullptr }; //object_ptr _dropArea = { nullptr }; base::Timer _refreshStatusTimer; diff --git a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp index bf3e16f92..b7eb2ad2c 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp @@ -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 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 diff --git a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h index 78b0d4acc..4d462c02c 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h +++ b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h @@ -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 user() const; private: Type _type; - QSize _fakeScamSize; }; diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index 939e07fe5..3f40faf6d 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -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" diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index 805d534f6..5672ba627 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/flags.h" + #include #include @@ -90,13 +92,7 @@ rpl::producer> MigratedOrMeValue( [[nodiscard]] rpl::producer AllowedReactionsCountValue( not_null peer); -enum class Badge { - None, - Verified, - Premium, - Scam, - Fake, -}; +enum class Badge; [[nodiscard]] rpl::producer BadgeValue(not_null peer); [[nodiscard]] rpl::producer EmojiStatusIdValue( not_null peer); diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 9bf34ae08..5f82bc462 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -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; diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index 30124c018..b8307e3db 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -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 button, not_null session, rpl::producer &&text, - bool hasUnread); + bool hasUnread, + Fn animationPaused); private: rpl::variable _text; @@ -79,7 +81,7 @@ private: rpl::event_stream _premiumWidth; QPointer _unread; - QPointer _premium; + Info::Profile::BadgeView _badge; }; @@ -88,9 +90,16 @@ ComposedBadge::ComposedBadge( not_null button, not_null session, rpl::producer &&text, - bool hasUnread) + bool hasUnread, + Fn 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(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 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(); diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 506127de2..a83514ec6 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -85,12 +85,13 @@ private: const not_null _controller; const not_null _user; + Info::Profile::BadgeView _badge; + Info::Profile::EmojiStatusPanel _emojiStatusPanel; object_ptr _userpic; object_ptr _name = { nullptr }; object_ptr _phone = { nullptr }; object_ptr _username = { nullptr }; - object_ptr _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) { diff --git a/Telegram/SourceFiles/ui/unread_badge.cpp b/Telegram/SourceFiles/ui/unread_badge.cpp index b149ad734..ca16cf82f 100644 --- a/Telegram/SourceFiles/ui/unread_badge.cpp +++ b/Telegram/SourceFiles/ui/unread_badge.cpp @@ -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; } diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index e3c372c64..2c9b24589 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -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 rightSkipValue() const { + return _rightSkip.value(); } private: @@ -130,7 +134,7 @@ private: void validateUnreadBadge(); [[nodiscard]] QString computeUnreadBadge() const; - int _rightSkip = 0; + rpl::variable _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( + this, + st::settingsInfoPeerBadge, + controller->session().user(), + [=] { return controller->isGifPausedAtLeastFor(GifPauseReason::Layer); }, + Info::Profile::Badge::Premium)) +, _emojiStatusPanel(std::make_unique()) , _scroll(this, st::defaultSolidScroll) , _inner(_scroll->setOwnedWidget( object_ptr(_scroll.data()))) @@ -416,6 +428,16 @@ MainMenu::MainMenu( controller->show(Box()); })); + 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); diff --git a/Telegram/SourceFiles/window/window_main_menu.h b/Telegram/SourceFiles/window/window_main_menu.h index 16528923e..2e05ef3c2 100644 --- a/Telegram/SourceFiles/window/window_main_menu.h +++ b/Telegram/SourceFiles/window/window_main_menu.h @@ -28,6 +28,11 @@ template 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 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 _controller; object_ptr _userpicButton; - Ui::PeerBadge _badge; Ui::Text::String _name; int _nameVersion = 0; object_ptr _toggleAccounts; + std::unique_ptr _badge; + std::unique_ptr _emojiStatusPanel; object_ptr _resetScaleButton = { nullptr }; object_ptr _scroll; not_null _inner;