Added initial list of subscriptions to credits settings section.

This commit is contained in:
23rd 2024-08-09 22:59:13 +03:00 committed by John Preston
parent ec93a91db2
commit 70d5dd8b71
4 changed files with 211 additions and 29 deletions
Telegram

View file

@ -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.";

View file

@ -1418,7 +1418,7 @@ object_ptr<Ui::BoxContent> 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,

View file

@ -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<QImage*> creditIcon;
int rowHeight = 0;
Fn<void(not_null<PeerListRow*>)> 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<QImage*> 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<PeerListRow*> row) {
delegate()->peerListUpdateRow(row);
},
};
if (const auto peerId = PeerId(i.barePeerId + s.barePeerId)) {
const auto peer = session().data().peer(peerId);
return std::make_unique<CreditsRow>(peer, descriptor);
} else {
return std::make_unique<CreditsRow>(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<PeerListRow*> row) {
delegate()->peerListUpdateRow(row);
},
};
if (const auto peerId = PeerId(item.barePeerId)) {
const auto peer = session().data().peer(peerId);
return std::make_unique<CreditsRow>(peer, descriptor);
} else {
return std::make_unique<CreditsRow>(descriptor);
}
}();
delegate()->peerListAppendRow(std::move(row));
delegate()->peerListAppendRow(create(item, {}));
}
for (const auto &item : slice.subscriptions) {
delegate()->peerListAppendRow(create({}, item));
}
delegate()->peerListRefreshRows();
}

View file

@ -71,6 +71,7 @@ private:
void setupContent();
void setupOptions(not_null<Ui::VerticalLayout*> container);
void setupHistory(not_null<Ui::VerticalLayout*> container);
void setupSubscriptions(not_null<Ui::VerticalLayout*> container);
const not_null<Window::SessionController*> _controller;
@ -124,6 +125,66 @@ void Credits::setStepDataReference(std::any &data) {
}
}
void Credits::setupSubscriptions(not_null<Ui::VerticalLayout*> container) {
const auto history = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(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<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(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<rpl::lifetime>();
{
using Api = Api::CreditsHistory;
const auto apiFull = apiLifetime->make_state<Api>(self, true, true);
apiFull->requestSubscriptions({}, [=](Data::CreditsStatusSlice d) {
fill(std::move(d));
});
}
}
void Credits::setupHistory(not_null<Ui::VerticalLayout*> container) {
const auto history = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
@ -132,7 +193,7 @@ void Credits::setupHistory(not_null<Ui::VerticalLayout*> 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<PeerData*> premiumBot,
@ -306,6 +367,7 @@ void Credits::setupContent() {
});
}
setupSubscriptions(content);
setupHistory(content);
Ui::ResizeFitChild(this, content);