From 78246aada7522d6a7675f89836b23b6e33e1b4cb Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 31 May 2022 13:32:07 +0300 Subject: [PATCH] Added box for premium accounts limits. --- Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/boxes/boxes.style | 14 ++ .../SourceFiles/boxes/premium_limits_box.cpp | 108 +++++++++++- .../SourceFiles/boxes/premium_limits_box.h | 3 + .../ui/effects/premium_graphics.cpp | 161 +++++++++++++++++- .../SourceFiles/ui/effects/premium_graphics.h | 27 ++- 6 files changed, 308 insertions(+), 9 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7a3eb4321..7398be858 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1698,6 +1698,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone."; "lng_premium_summary_button" = "Subscribe for {cost} per month"; +"lng_accounts_limit_title" = "Limit Reached"; +"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts. You can free one space by subscribing to **Telegram Premium** with on of these connected accounts:"; +"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts. You can free one space by subscribing to **Telegram Premium** with on of these connected accounts:"; + "lng_group_about_header" = "You have created a group."; "lng_group_about_text" = "Groups can have:"; "lng_group_about1" = "Up to 100,000 members"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 81bad87f8..5470a25cb 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -1096,3 +1096,17 @@ premiumIconFolders: icon {{ "limits/folders", settingsIconFg }}; premiumIconGroups: icon {{ "limits/groups", settingsIconFg }}; premiumIconLinks: icon {{ "limits/links", settingsIconFg }}; premiumIconPins: icon {{ "limits/pins", settingsIconFg }}; + +premiumAccountsCheckbox: RoundImageCheckbox(defaultPeerListCheckbox) { + imageRadius: 27px; + imageSmallRadius: 23px; + check: RoundCheckbox(defaultRoundCheckbox) { + size: 0px; + } +} +premiumAccountsLabelSize: size(22px, 15px); +premiumAccountsLabelPadding: margins(2px, 2px, 2px, 2px); +premiumAccountsLabelRadius: 6; +premiumAccountsNameTop: 13px; +premiumAccountsPadding: margins(0px, 20px, 0px, 14px); +premiumAccountsHeight: 105px; diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp index 4fae83754..9b529ee1d 100644 --- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp @@ -10,13 +10,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "ui/controls/peer_list_dummy.h" #include "ui/effects/premium_graphics.h" -#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" #include "ui/wrap/padding_wrap.h" #include "ui/text/text_utilities.h" #include "ui/toasts/common_toasts.h" #include "main/main_session.h" #include "main/main_account.h" #include "main/main_app_config.h" +#include "main/main_domain.h" #include "boxes/peer_list_controllers.h" #include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox #include "window/window_session_controller.h" @@ -879,6 +880,111 @@ void FileSizeLimitBox( premium); } +void AccountsLimitBox( + not_null box, + not_null session) { + const auto premium = session->premium(); + + const auto defaultLimit = Main::Domain::kMaxAccounts; + const auto premiumLimit = Main::Domain::kPremiumMaxAccounts; + + const auto &accounts = session->domain().accounts(); + const auto current = int(accounts.size()); + + auto text = tr::lng_accounts_limit1( + lt_count, + rpl::single(premiumLimit), + Ui::Text::RichLangValue); + + box->setWidth(st::boxWideWidth); + + const auto top = box->verticalLayout(); + const auto group = std::make_shared(0); + + Settings::AddSkip(top, st::premiumInfographicPadding.top()); + Ui::Premium::AddBubbleRow( + top, + BoxShowFinishes(box), + 0, + current, + (current == premiumLimit) ? premiumLimit : (current * 2), + std::nullopt, + &st::premiumIconFiles); + Settings::AddSkip(top, st::premiumLineTextSkip); + Ui::Premium::AddLimitRow(top, 0, std::nullopt, defaultLimit); + Settings::AddSkip(top, st::premiumInfographicPadding.bottom()); + + box->setTitle(tr::lng_accounts_limit_title()); + + auto padding = st::boxPadding; + padding.setTop(padding.bottom()); + top->add( + object_ptr( + box, + std::move(text), + st::aboutRevokePublicLabel), + padding); + + + if (premium) { + box->addButton(tr::lng_box_ok(), [=] { + box->closeBox(); + }); + } else { + auto switchingLifetime = std::make_shared(); + box->addButton(tr::lng_continue(), [=]() mutable { + const auto ref = QString(); + + const auto &accounts = session->domain().accounts(); + const auto wasAccount = &session->account(); + const auto nowAccount = accounts[group->value()].account.get(); + if (wasAccount == nowAccount) { + Settings::ShowPremium(session, ref); + return; + } + + if (*switchingLifetime) { + return; + } + *switchingLifetime = session->domain().activeSessionChanges( + ) | rpl::start_with_next([=](Main::Session *session) mutable { + if (session) { + Settings::ShowPremium(session, ref); + } + if (switchingLifetime) { + base::take(switchingLifetime)->destroy(); + } + }); + session->domain().activate(nowAccount); + }); + } + + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); + + using Args = Ui::Premium::AccountsRowArgs; + + auto &&entries = ranges::views::all( + accounts + ) | ranges::views::transform([&]( + const Main::Domain::AccountWithIndex &d) { + const auto user = d.account->session().user(); + return Args::Entry{ user->name, PaintUserpicCallback(user, false) }; + }); + + auto args = Args{ + .group = group, + .st = st::premiumAccountsCheckbox, + .stName = st::shareBoxListItem.nameStyle, + .stNameFg = st::shareBoxListItem.nameFg, + .entries = std::move(entries) | ranges::to_vector, + }; + box->addSkip(st::premiumAccountsPadding.top()); + Ui::Premium::AddAccountsRow(box->verticalLayout(), std::move(args)); + box->addSkip(st::premiumAccountsPadding.bottom()); +} + QString LimitsPremiumRef(const QString &addition) { return "double_limits__" + addition; } diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.h b/Telegram/SourceFiles/boxes/premium_limits_box.h index ef97e9967..d050f772c 100644 --- a/Telegram/SourceFiles/boxes/premium_limits_box.h +++ b/Telegram/SourceFiles/boxes/premium_limits_box.h @@ -49,6 +49,9 @@ void CaptionLimitReachedBox( void FileSizeLimitBox( not_null box, not_null session); +void AccountsLimitBox( + not_null box, + not_null session); [[nodiscard]] QString LimitsPremiumRef(const QString &addition); diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index 90ac1b1b6..967fac26c 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -11,18 +11,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/effects/gradient.h" #include "ui/effects/numbers_animation.h" +#include "ui/text/text_options.h" +#include "ui/widgets/checkbox.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/vertical_layout.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" #include "styles/style_widgets.h" +#include + namespace Ui { namespace Premium { namespace { using TextFactory = Fn; +constexpr auto kBubbleRadiusSubtractor = 2; constexpr auto kDeflectionSmall = 20.; constexpr auto kDeflection = 30.; @@ -133,7 +138,7 @@ int Bubble::height() const { } int Bubble::bubbleRadius() const { - return (_height - _tailSize.height()) / 2; + return (_height - _tailSize.height()) / 2 - kBubbleRadiusSubtractor; } int Bubble::filledWidth() const { @@ -430,7 +435,11 @@ void BubbleWidget::paintEvent(QPaintEvent *e) { class Line final : public Ui::RpWidget { public: - Line(not_null parent, int max, TextFactory textFactory); + Line( + not_null parent, + int max, + TextFactory textFactory, + int min); protected: void paintEvent(QPaintEvent *event) override; @@ -447,14 +456,20 @@ private: Ui::Text::String _leftText; Ui::Text::String _rightText; Ui::Text::String _rightLabel; + Ui::Text::String _leftLabel; }; -Line::Line(not_null parent, int max, TextFactory textFactory) +Line::Line( + not_null parent, + int max, + TextFactory textFactory, + int min) : Ui::RpWidget(parent) , _leftText(st::defaultTextStyle, tr::lng_premium_free(tr::now)) , _rightText(st::defaultTextStyle, tr::lng_premium(tr::now)) -, _rightLabel(st::defaultTextStyle, textFactory(max)) { +, _rightLabel(st::defaultTextStyle, max ? textFactory(max) : QString()) +, _leftLabel(st::defaultTextStyle, min ? textFactory(min) : QString()) { resize(width(), st::requestsAcceptButton.height); sizeValue( @@ -478,6 +493,13 @@ void Line::paintEvent(QPaintEvent *event) { const auto textTop = (height() - _leftText.minHeight()) / 2; p.setPen(st::windowFg); + _leftLabel.drawRight( + p, + textPadding, + textTop, + _leftWidth - textPadding, + _leftWidth, + style::al_right); _leftText.drawLeft( p, textPadding, @@ -572,12 +594,139 @@ void AddBubbleRow( void AddLimitRow( not_null parent, int max, - std::optional> phrase) { + std::optional> phrase, + int min) { const auto line = parent->add( - object_ptr(parent, max, ProcessTextFactory(phrase)), + object_ptr(parent, max, ProcessTextFactory(phrase), min), st::boxRowPadding); } +void AddAccountsRow( + not_null parent, + AccountsRowArgs &&args) { + const auto container = parent->add( + object_ptr(parent, st::premiumAccountsHeight), + st::boxRowPadding); + + struct Account { + not_null widget; + Ui::RoundImageCheckbox checkbox; + Ui::Text::String name; + QPixmap badge; + }; + struct State { + std::vector accounts; + }; + const auto state = container->lifetime().make_state(); + const auto group = args.group; + + group->setChangedCallback([=](int value) { + for (auto i = 0; i < state->accounts.size(); i++) { + state->accounts[i].checkbox.setChecked(i == value); + } + }); + + const auto imageRadius = args.st.imageRadius; + const auto checkSelectWidth = args.st.selectWidth; + const auto nameFg = args.stNameFg; + + const auto cacheBadge = [=](int center) { + const auto &padding = st::premiumAccountsLabelPadding; + const auto size = st::premiumAccountsLabelSize + + QSize( + padding.left() + padding.right(), + padding.top() + padding.bottom()); + auto badge = QPixmap(size * style::DevicePixelRatio()); + badge.setDevicePixelRatio(style::DevicePixelRatio()); + badge.fill(Qt::transparent); + + Painter p(&badge); + PainterHighQualityEnabler hq(p); + + p.setPen(Qt::NoPen); + const auto rectOut = QRect(QPoint(), size); + const auto rectIn = rectOut - padding; + + const auto radius = st::premiumAccountsLabelRadius; + p.setBrush(st::premiumButtonFg); + p.drawRoundedRect(rectOut, radius, radius); + + const auto left = center - rectIn.width() / 2; + p.setBrush(QBrush(ComputeGradient(container, left, rectIn.width()))); + p.drawRoundedRect(rectIn, radius / 2, radius / 2); + + p.setPen(st::premiumButtonFg); + p.setFont(st::semiboldFont); + p.drawText(rectIn, u"+1"_q, style::al_center); + + return badge; + }; + + for (auto &entry : args.entries) { + const auto widget = Ui::CreateChild(container); + auto name = Ui::Text::String(imageRadius * 2); + name.setText(args.stName, entry.name, Ui::NameTextOptions()); + state->accounts.push_back(Account{ + .widget = widget, + .checkbox = Ui::RoundImageCheckbox( + args.st, + [=] { widget->update(); }, + base::take(entry.paintRoundImage)), + .name = std::move(name), + }); + const auto index = int(state->accounts.size()) - 1; + state->accounts[index].checkbox.setChecked(index == group->value()); + + widget->paintRequest( + ) | rpl::start_with_next([=] { + Painter p(widget); + const auto width = widget->width(); + const auto photoLeft = (width - (imageRadius * 2)) / 2; + const auto photoTop = checkSelectWidth; + auto &account = state->accounts[index]; + account.checkbox.paint(p, photoLeft, photoTop, width); + + const auto &badgeSize = account.badge.size() + / style::DevicePixelRatio(); + p.drawPixmap( + (width - badgeSize.width()) / 2, + photoTop + (imageRadius * 2) - badgeSize.height() / 2, + account.badge); + + p.setPen(nameFg); + p.setBrush(Qt::NoBrush); + account.name.drawLeftElided( + p, + 0, + photoTop + imageRadius * 2 + st::premiumAccountsNameTop, + width, + width, + 2, + style::al_top, + 0, + -1, + 0, + true); + }, widget->lifetime()); + + widget->setClickedCallback([=] { + group->setValue(index); + }); + } + + container->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + const auto count = state->accounts.size(); + const auto columnWidth = size.width() / count; + for (auto i = 0; i < count; i++) { + state->accounts[i].widget->resize(columnWidth, size.height()); + const auto left = columnWidth * i; + state->accounts[i].widget->moveToLeft(left, 0); + state->accounts[i].badge = cacheBadge(left + columnWidth / 2); + } + }, container->lifetime()); +} + QGradientStops LimitGradientStops() { return { { 0.0, st::premiumButtonBg1->c }, diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.h b/Telegram/SourceFiles/ui/effects/premium_graphics.h index 6f5a94298..786a629c3 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.h +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include +#include "ui/effects/round_checkbox.h" namespace tr { template @@ -16,8 +16,14 @@ struct phrase; enum lngtag_count : int; +namespace style { +struct RoundImageCheckbox; +struct TextStyle; +} // namespace style + namespace Ui { +class RadiobuttonGroup; class VerticalLayout; namespace Premium { @@ -34,7 +40,24 @@ void AddBubbleRow( void AddLimitRow( not_null parent, int max, - std::optional> phrase); + std::optional> phrase, + int min = 0); + +struct AccountsRowArgs final { + std::shared_ptr group; + const style::RoundImageCheckbox &st; + const style::TextStyle &stName; + const style::color &stNameFg; + struct Entry final { + QString name; + Ui::RoundImageCheckbox::PaintRoundImage paintRoundImage; + }; + std::vector entries; +}; + +void AddAccountsRow( + not_null parent, + AccountsRowArgs &&args); [[nodiscard]] QGradientStops LimitGradientStops(); [[nodiscard]] QGradientStops ButtonGradientStops();