From 70d5dd8b7140e11144979314e08d934f248f69ea Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 9 Aug 2024 22:59:13 +0300 Subject: [PATCH] Added initial list of subscriptions to credits settings section. --- Telegram/Resources/langs/lang.strings | 24 +++ .../boxes/peers/edit_peer_invite_link.cpp | 2 +- .../info_statistics_list_controllers.cpp | 150 ++++++++++++++---- .../SourceFiles/settings/settings_credits.cpp | 64 +++++++- 4 files changed, 211 insertions(+), 29 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 40f11aac0..2e1dbeab3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2415,6 +2415,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_about_link" = "here"; "lng_credits_box_history_entry_reaction_name" = "Star Reaction"; "lng_credits_box_history_entry_subscription" = "Monthly subscription fee"; + +"lng_credits_subscription_section" = "My subscriptions"; +"lng_credits_box_subscription_title" = "Subscription"; +"lng_credits_subscription_subtitle" = "{emoji} {cost} / month"; + +"lng_credits_subscription_row_to" = "Subscription"; +"lng_credits_subscription_row_from" = "Subscribed"; + +"lng_credits_subscription_row_next_on" = "Renews"; +"lng_credits_subscription_row_next_off" = "Expires"; +"lng_credits_subscription_row_next_none" = "Expired"; + +"lng_credits_subscription_on_button" = "Cancel Subscription"; +"lng_credits_subscription_on_about" = "If you cancel now, you will still be able to access your subscription until {date}."; + +"lng_credits_subscription_off_button" = "Renew Subscription"; +"lng_credits_subscription_off_about" = "You have cancelled your subscription."; + +"lng_credits_subscription_status_on" = "renews on {date}"; +"lng_credits_subscription_status_off" = "expires on {date}"; +"lng_credits_subscription_status_none" = "expired on {date}"; +"lng_credits_subscription_status_off_right" = "cancelled"; +"lng_credits_subscription_status_none_right" = "expired"; + "lng_credits_small_balance_title#one" = "{count} Star Needed"; "lng_credits_small_balance_title#other" = "{count} Stars Needed"; "lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps."; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 0f1cd6bff..fa0781719 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -1418,7 +1418,7 @@ object_ptr EditLinkBox( .label = data.label, .expireDate = data.expireDate, .usageLimit = data.usageLimit, - .subscriptionCredits = data.subscription.period, + .subscriptionCredits = int(data.subscription.credits), .requestApproval = data.requestApproval, .isGroup = isGroup, .isPublic = isPublic, diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 825dd620f..82de1464b 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -10,11 +10,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "api/api_statistics.h" #include "boxes/peer_list_controllers.h" +#include "core/ui_integration.h" // Core::MarkedTextContext. #include "data/data_channel.h" #include "data/data_credits.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_user.h" +#include "data/stickers/data_custom_emoji.h" #include "history/history_item.h" #include "info/channel_statistics/boosts/giveaway/boost_badge.h" #include "lang/lang_keys.h" @@ -30,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "styles/style_boxes.h" #include "styles/style_credits.h" #include "styles/style_dialogs.h" // dialogsStoriesFull. #include "styles/style_layers.h" // boxRowPadding. @@ -720,6 +723,7 @@ class CreditsRow final : public PeerListRow { public: struct Descriptor final { Data::CreditsHistoryEntry entry; + Data::SubscriptionEntry subscription; not_null creditIcon; int rowHeight = 0; Fn)> updateCallback; @@ -729,6 +733,7 @@ public: CreditsRow(const Descriptor &descriptor); [[nodiscard]] const Data::CreditsHistoryEntry &entry() const; + [[nodiscard]] const Data::SubscriptionEntry &subscription() const; [[nodiscard]] QString generateName() override; [[nodiscard]] PaintRoundImageCallback generatePaintUserpicCallback( @@ -749,6 +754,7 @@ private: void init(); const Data::CreditsHistoryEntry _entry; + const Data::SubscriptionEntry _subscription; not_null const _creditIcon; const int _rowHeight; @@ -766,15 +772,16 @@ CreditsRow::CreditsRow( const Descriptor &descriptor) : PeerListRow(peer, UniqueRowIdFromEntry(descriptor.entry)) , _entry(descriptor.entry) +, _subscription(descriptor.subscription) , _creditIcon(descriptor.creditIcon) , _rowHeight(descriptor.rowHeight) { const auto callback = Ui::PaintPreviewCallback( &peer->session(), _entry); if (callback) { - _paintUserpicCallback = callback(crl::guard(&_guard, [this, update = descriptor.updateCallback] { - update(this); - })); + _paintUserpicCallback = callback(crl::guard( + &_guard, + [this, update = descriptor.updateCallback] { update(this); })); } init(); } @@ -782,13 +789,15 @@ CreditsRow::CreditsRow( CreditsRow::CreditsRow(const Descriptor &descriptor) : PeerListRow(UniqueRowIdFromEntry(descriptor.entry)) , _entry(descriptor.entry) +, _subscription(descriptor.subscription) , _creditIcon(descriptor.creditIcon) , _rowHeight(descriptor.rowHeight) { init(); } void CreditsRow::init() { - const auto name = !PeerListRow::special() + const auto isSpecial = PeerListRow::special(); + const auto name = !isSpecial ? PeerListRow::generateName() : Ui::GenerateEntryName(_entry).text; _name = _entry.reaction @@ -809,18 +818,39 @@ void CreditsRow::init() { ? (joiner + tr::lng_credits_box_history_entry_subscription(tr::now)) : QString()) - + ((_entry.gift && PeerListRow::special()) + + ((_entry.gift && isSpecial) ? (joiner + tr::lng_credits_box_history_entry_anonymous(tr::now)) : ((_name == name) ? QString() : (joiner + name)))); - { + if (_subscription) { + PeerListRow::setCustomStatus((_subscription.expired + ? tr::lng_credits_subscription_status_none + : _subscription.cancelled + ? tr::lng_credits_subscription_status_off + : tr::lng_credits_subscription_status_on)( + tr::now, + lt_date, + langDayOfMonthFull(_subscription.until.date()))); + } + if (_entry) { constexpr auto kMinus = QChar(0x2212); _rightText.setText( st::semiboldTextStyle, (_entry.in ? QChar('+') : kMinus) + Lang::FormatCountDecimal(std::abs(int64(_entry.credits)))); + } else if (_subscription.subscription.credits && !isSpecial) { + const auto peer = PeerListRow::peer(); + _rightText.setMarkedText( + st::semiboldTextStyle, + peer->owner().customEmojiManager().creditsEmoji().append( + Lang::FormatCountDecimal(_subscription.subscription.credits)), + kMarkupTextOptions, + Core::MarkedTextContext{ + .session = &peer->session(), + .customEmojiRepaint = [] {}, + }); } if (!_paintUserpicCallback) { - _paintUserpicCallback = !PeerListRow::special() + _paintUserpicCallback = !isSpecial ? PeerListRow::generatePaintUserpicCallback(false) : Ui::GenerateCreditsPaintUserpicCallback(_entry); } @@ -830,6 +860,10 @@ const Data::CreditsHistoryEntry &CreditsRow::entry() const { return _entry; } +const Data::SubscriptionEntry &CreditsRow::subscription() const { + return _subscription; +} + QString CreditsRow::generateName() { return _entry.title.isEmpty() ? _name : _entry.title; } @@ -839,6 +873,20 @@ PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) { } QSize CreditsRow::rightActionSize() const { + if (_subscription.cancelled || _subscription.expired) { + const auto text = _subscription.cancelled + ? tr::lng_credits_subscription_status_off_right(tr::now) + : tr::lng_credits_subscription_status_none_right(tr::now); + return QSize( + st::contactsStatusFont->width(text) + st::boxRowPadding.right(), + _rowHeight); + } else if (_subscription) { + return QSize( + _rightText.maxWidth() + st::boxRowPadding.right(), + _rowHeight); + } else if (!_entry && !_subscription) { + return QSize(); + } return QSize( _rightText.maxWidth() + (_creditIcon->width() / style::DevicePixelRatio()) @@ -863,6 +911,44 @@ void CreditsRow::rightActionPaint( bool selected, bool actionSelected) { const auto &font = _rightText.style()->font; + if (_subscription) { + const auto &statusFont = st::contactsStatusFont; + const auto &st = st::boostsListBox.item; + const auto textHeight = font->height + statusFont->height; + const auto skip = (_rowHeight - textHeight) / 2; + const auto rightSkip = st::boxRowPadding.right(); + if (_subscription.cancelled || _subscription.expired) { + y += _rowHeight / 2; + p.setFont(statusFont); + p.setPen(st::attentionButtonFg); + p.drawTextRight( + rightSkip, + y - statusFont->height / 2, + outerWidth, + _subscription.expired + ? tr::lng_credits_subscription_status_none_right(tr::now) + : tr::lng_credits_subscription_status_off_right(tr::now)); + return; + } + + p.setPen(st.statusFg); + p.setFont(statusFont); + p.drawTextRight( + rightSkip, + y + _rowHeight - skip - statusFont->height, + outerWidth, + tr::lng_group_invite_joined_right(tr::now)); + + p.setPen(st.nameFg); + _rightText.draw(p, Ui::Text::PaintContext{ + .position = QPoint( + outerWidth - _rightText.maxWidth() - rightSkip, + y + skip), + .outerWidth = outerWidth, + .availableWidth = outerWidth, + }); + return; + } y += _rowHeight / 2; p.setPen(_entry.pending ? st::creditsStroke @@ -931,10 +1017,14 @@ bool CreditsController::skipRequest() const { void CreditsController::requestNext() { _requesting = true; - _api.request(_apiToken, [=](const Data::CreditsStatusSlice &s) { + const auto done = [=](const Data::CreditsStatusSlice &s) { _requesting = false; applySlice(s); - }); + }; + if (!_firstSlice.subscriptions.empty()) { + return _api.requestSubscriptions(_apiToken, done); + } + _api.request(_apiToken, done); } void CreditsController::prepare() { @@ -947,26 +1037,32 @@ void CreditsController::loadMoreRows() { void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { _allLoaded = slice.allLoaded; - _apiToken = slice.token; + _apiToken = slice.tokenSubscriptions; + auto create = [&]( + const Data::CreditsHistoryEntry &i, + const Data::SubscriptionEntry &s) { + const auto descriptor = CreditsRow::Descriptor{ + .entry = i, + .subscription = s, + .creditIcon = _creditIcon, + .rowHeight = computeListSt().item.height, + .updateCallback = [=](not_null row) { + delegate()->peerListUpdateRow(row); + }, + }; + if (const auto peerId = PeerId(i.barePeerId + s.barePeerId)) { + const auto peer = session().data().peer(peerId); + return std::make_unique(peer, descriptor); + } else { + return std::make_unique(descriptor); + } + }; for (const auto &item : slice.list) { - auto row = [&] { - const auto descriptor = CreditsRow::Descriptor{ - .entry = item, - .creditIcon = _creditIcon, - .rowHeight = computeListSt().item.height, - .updateCallback = [=](not_null row) { - delegate()->peerListUpdateRow(row); - }, - }; - if (const auto peerId = PeerId(item.barePeerId)) { - const auto peer = session().data().peer(peerId); - return std::make_unique(peer, descriptor); - } else { - return std::make_unique(descriptor); - } - }(); - delegate()->peerListAppendRow(std::move(row)); + delegate()->peerListAppendRow(create(item, {})); + } + for (const auto &item : slice.subscriptions) { + delegate()->peerListAppendRow(create({}, item)); } delegate()->peerListRefreshRows(); } diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index d413551f0..ea3cc6bdb 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -71,6 +71,7 @@ private: void setupContent(); void setupOptions(not_null container); void setupHistory(not_null container); + void setupSubscriptions(not_null container); const not_null _controller; @@ -124,6 +125,66 @@ void Credits::setStepDataReference(std::any &data) { } } +void Credits::setupSubscriptions(not_null container) { + const auto history = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto content = history->entity(); + const auto self = _controller->session().user(); + + const auto fill = [=](const Data::CreditsStatusSlice &fullSlice) { + const auto inner = content; + if (fullSlice.subscriptions.empty()) { + return; + } + Ui::AddSkip(inner); + Ui::AddSubsectionTitle( + inner, + tr::lng_credits_subscription_section(), + { 0, 0, 0, -st::settingsPremiumOptionsPadding.bottom() }); + + const auto fullWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + + const auto controller = _controller->parentController(); + const auto entryClicked = [=](const Data::CreditsHistoryEntry &e) { + controller->uiShow()->show(Box( + ReceiptCreditsBox, + controller, + nullptr, + e)); + }; + + Info::Statistics::AddCreditsHistoryList( + controller->uiShow(), + fullSlice, + fullWrap->entity(), + entryClicked, + self, + &_star, + true, + true); + + Ui::AddSkip(inner); + Ui::AddSkip(inner); + Ui::AddDivider(inner); + + inner->resizeToWidth(container->width()); + }; + + const auto apiLifetime = content->lifetime().make_state(); + { + using Api = Api::CreditsHistory; + const auto apiFull = apiLifetime->make_state(self, true, true); + apiFull->requestSubscriptions({}, [=](Data::CreditsStatusSlice d) { + fill(std::move(d)); + }); + } +} + void Credits::setupHistory(not_null container) { const auto history = container->add( object_ptr>( @@ -132,7 +193,7 @@ void Credits::setupHistory(not_null container) { const auto content = history->entity(); const auto self = _controller->session().user(); - Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); + Ui::AddSkip(content); const auto fill = [=]( not_null premiumBot, @@ -306,6 +367,7 @@ void Credits::setupContent() { }); } + setupSubscriptions(content); setupHistory(content); Ui::ResizeFitChild(this, content);