Added ability to choose subscription option for Premium in Settings.

This commit is contained in:
23rd 2022-08-25 20:30:10 +03:00 committed by John Preston
parent 6f3d19914d
commit 149d92d224
8 changed files with 232 additions and 57 deletions

View file

@ -1702,6 +1702,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_unlock_stickers" = "Unlock Premium Stickers";
"lng_premium_unlock_emoji" = "Unlock Animated Emoji";
"lng_premium_subscribe_months_12" = "Annual";
"lng_premium_subscribe_months_6" = "Semiannual";
"lng_premium_subscribe_months_1" = "Monthly";
"lng_premium_subscribe_total" = "{cost} per year";
"lng_premium_subscribe_button" = "Subscribe for {cost} per month";
"lng_premium_emoji_status_title" = "{user} set this emoji from {link} as their current status.";
"lng_premium_emoji_status_about" = "Emoji status is a premium feature. Other features included in **Telegram Premium**:";
"lng_premium_emoji_status_button" = "Unlock Emoji Status";

View file

@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_premium_option.h"
#include "base/weak_ptr.h"
#include "core/click_handler_types.h" // ClickHandlerContext.
#include "core/local_url_handlers.h" // TryConvertUrlToLocal.
#include "data/data_changes.h"
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
#include "data/data_session.h"
@ -167,7 +165,11 @@ void GiftBox(
options[value].total);
state->buttonText.fire(std::move(text));
});
Ui::Premium::AddGiftOptions(buttonsParent, group, options);
Ui::Premium::AddGiftOptions(
buttonsParent,
group,
options,
st::premiumGiftOption);
// Footer.
auto terms = object_ptr<Ui::FlatLabel>(
@ -198,26 +200,17 @@ void GiftBox(
[] { return QString("gift"); },
state->buttonText.events(),
Ui::Premium::GiftGradientStops(),
[=] {
const auto value = group->value();
return (value < options.size() && value >= 0)
? options[value].botUrl
: QString();
},
});
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
button->resizeToWidth(boxWidth
- stButton.buttonPadding.left()
- stButton.buttonPadding.right());
button->setClickedCallback([=] {
const auto value = group->value();
Assert(value < options.size() && value >= 0);
const auto local = Core::TryConvertUrlToLocal(options[value].botUrl);
if (local.isEmpty()) {
return;
}
UrlClickHandler::Open(
local,
QVariant::fromValue(ClickHandlerContext{
.sessionWindow = base::make_weak(controller.get()),
.botStartAutoSubmit = true,
}));
});
box->setShowFinishedCallback([raw = button.data()]{
raw->startGlareAnimation();
});

View file

@ -489,6 +489,7 @@ settingsPremiumArrow: icon{{ "fast_to_original", menuIconFg }};
settingsPremiumArrowOver: icon{{ "fast_to_original", menuIconFgOver }};
settingsPremiumStatusPadding: margins(22px, 8px, 22px, 2px);
settingsPremiumOptionsPadding: margins(0px, 9px, 0px, 2px);
settingsPremiumTopHeight: 220px;
settingsPremiumUserHeight: 223px;
settingsPremiumUserTitlePadding: margins(0px, 16px, 0px, 6px);

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_lottie.h" // LottiePlayerFromDocument.
#include "core/application.h"
#include "core/click_handler_types.h"
#include "core/local_url_handlers.h" // Core::TryConvertUrlToLocal.
#include "core/ui_integration.h" // MarkedTextContext.
#include "data/data_document.h"
#include "data/data_document_media.h"
@ -39,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h" // Ui::RadiobuttonGroup.
#include "ui/widgets/gradient_round_button.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
@ -69,6 +71,33 @@ constexpr auto kTitleAdditionalScale = 0.15;
return u":/gui/icons/settings/star.svg"_q;
}
[[nodiscard]] Data::SubscriptionOptions SubscriptionOptionsForRows(
Data::SubscriptionOptions result) {
for (auto &option : result) {
const auto total = option.costTotal;
const auto perMonth = option.costPerMonth;
option.costTotal = tr::lng_premium_gift_per(
tr::now,
lt_cost,
perMonth);
option.costPerMonth = tr::lng_premium_subscribe_total(
tr::now,
lt_cost,
total);
if (option.duration == tr::lng_months(tr::now, lt_count, 1)) {
option.costPerMonth = QString();
option.duration = tr::lng_premium_subscribe_months_1(tr::now);
} else if (option.duration == tr::lng_months(tr::now, lt_count, 6)) {
option.duration = tr::lng_premium_subscribe_months_6(tr::now);
} else if (option.duration == tr::lng_years(tr::now, lt_count, 1)) {
option.duration = tr::lng_premium_subscribe_months_12(tr::now);
}
}
return result;
}
namespace Ref {
namespace Gift {
@ -968,6 +997,9 @@ public:
private:
void setupContent();
void setupSubscriptionOptions(
not_null<Ui::VerticalLayout*> container,
int lastSkip);
const not_null<Window::SessionController*> _controller;
const QString _ref;
@ -979,8 +1011,11 @@ private:
rpl::variable<Info::Wrap> _wrap;
Fn<void(bool)> _setPaused;
std::shared_ptr<Ui::RadiobuttonGroup> _radioGroup;
rpl::event_stream<> _showBack;
rpl::event_stream<> _showFinished;
rpl::event_stream<QString> _buttonText;
};
@ -989,7 +1024,8 @@ Premium::Premium(
not_null<Window::SessionController*> controller)
: Section(parent)
, _controller(controller)
, _ref(ResolveRef(controller->premiumRef())) {
, _ref(ResolveRef(controller->premiumRef()))
, _radioGroup(std::make_shared<Ui::RadiobuttonGroup>()) {
setupContent();
_controller->session().api().premium().reload();
}
@ -1016,6 +1052,47 @@ void Premium::setStepDataReference(std::any &data) {
}
}
void Premium::setupSubscriptionOptions(
not_null<Ui::VerticalLayout*> container,
int lastSkip) {
const auto options = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
const auto skip = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
const auto content = options->entity();
AddSkip(content, st::settingsPremiumOptionsPadding.top());
Ui::Premium::AddGiftOptions(
content,
_radioGroup,
SubscriptionOptionsForRows(
_controller->session().api().premium().subscriptionOptions()),
st::premiumSubscriptionOption,
true);
AddSkip(content, st::settingsPremiumOptionsPadding.bottom());
AddDivider(content);
AddSkip(content, lastSkip - st::settingsSectionSkip);
AddSkip(skip->entity(), lastSkip);
auto toggleOn = rpl::combine(
Data::AmPremiumValue(&_controller->session()),
rpl::single(!!(Ref::EmojiStatus::Parse(_ref)))
) | rpl::map([=](bool premium, bool isEmojiStatus) {
return !premium && !isEmojiStatus;
});
options->toggleOn(rpl::duplicate(toggleOn), anim::type::instant);
skip->toggleOn(std::move(
toggleOn
) | rpl::map([](bool value) { return !value; }), anim::type::instant);
}
void Premium::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
@ -1025,7 +1102,9 @@ void Premium::setupContent() {
const auto &titlePadding = st::settingsPremiumRowTitlePadding;
const auto &descriptionPadding = st::settingsPremiumRowAboutPadding;
AddSkip(content, stDefault.padding.top() + titlePadding.top());
setupSubscriptionOptions(
content,
stDefault.padding.top() + titlePadding.top());
auto entryMap = EntryMap();
auto iconContainers = std::vector<Ui::AbstractButton*>();
@ -1385,10 +1464,11 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
}
const auto emojiStatusData = Ref::EmojiStatus::Parse(_ref);
const auto session = &_controller->session();
auto buttonText = [&]() -> std::optional<rpl::producer<QString>> {
if (emojiStatusData) {
auto &data = _controller->session().data();
auto &data = session->data();
if (const auto peer = data.peer(emojiStatusData.peerId)) {
return Info::Profile::EmojiStatusIdValue(
peer
@ -1399,7 +1479,7 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
}) | rpl::flatten_latest();
}
}
return std::nullopt;
return _buttonText.events();
}();
_subscribe = CreateSubscribeButton({
@ -1407,6 +1487,13 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
content,
[ref = _ref] { return ref; },
std::move(buttonText),
std::nullopt,
[=, options = session->api().premium().subscriptionOptions()] {
const auto value = _radioGroup->value();
return (value < options.size() && value >= 0)
? options[value].botUrl
: QString();
},
});
if (emojiStatusData) {
// "Learn More" should open the general Premium Settings
@ -1419,6 +1506,18 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
ShowPremium(controller, QString());
controller->setPremiumRef(ref);
});
} else {
_radioGroup->setChangedCallback([=](int value) {
const auto options =
_controller->session().api().premium().subscriptionOptions();
Expects(value < options.size() && value >= 0);
auto text = tr::lng_premium_subscribe_button(
tr::now,
lt_cost,
options[value].costPerMonth);
_buttonText.fire(std::move(text));
});
_radioGroup->setValue(0);
}
_showFinished.events(
@ -1432,7 +1531,6 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
_subscribe->resizeToWidth(width - padding.left() - padding.right());
}, _subscribe->lifetime());
const auto session = &_controller->session();
rpl::combine(
_subscribe->heightValue(),
Data::AmPremiumValue(session),
@ -1543,9 +1641,24 @@ not_null<Ui::GradientButton*> CreateSubscribeButton(
result->setClickedCallback([
controller = args.controller,
computeRef = args.computeRef] {
SendScreenAccept(controller);
StartPremiumPayment(controller, computeRef());
computeRef = args.computeRef,
computeBotUrl = args.computeBotUrl] {
const auto url = computeBotUrl ? QString() : computeBotUrl();
if (!url.isEmpty()) {
const auto local = Core::TryConvertUrlToLocal(url);
if (local.isEmpty()) {
return;
}
UrlClickHandler::Open(
local,
QVariant::fromValue(ClickHandlerContext{
.sessionWindow = base::make_weak(controller.get()),
.botStartAutoSubmit = true,
}));
} else {
SendScreenAccept(controller);
StartPremiumPayment(controller, computeRef());
}
});
const auto &st = st::premiumPreviewBox.button;

View file

@ -53,6 +53,7 @@ struct SubscribeButtonArgs final {
Fn<QString()> computeRef;
std::optional<rpl::producer<QString>> text;
std::optional<QGradientStops> gradientStops;
Fn<QString()> computeBotUrl; // nullable
};
[[nodiscard]] not_null<Ui::GradientButton*> CreateSubscribeButton(

View file

@ -97,17 +97,56 @@ premiumAccountsNameTop: 13px;
premiumAccountsPadding: margins(0px, 20px, 0px, 14px);
premiumAccountsHeight: 105px;
PremiumOption {
rowPadding: margins;
rowMargins: margins;
rowHeight: pixels;
borderWidth: pixels;
borderRadius: pixels;
subtitleTop: pixels;
textLeft: pixels;
badgeHeight: pixels;
badgeRadius: pixels;
badgeMargins: margins;
badgeShift: point;
}
premiumSubscriptionOption: PremiumOption {
rowPadding: margins(9px, 2px, 17px, 3px);
rowMargins: margins(14px, 0px, 5px, 0px);
rowHeight: 39px;
borderWidth: 0px;
borderRadius: 0px;
subtitleTop: 1px;
textLeft: 51px;
badgeHeight: 15px;
badgeRadius: 4px;
badgeMargins: margins(3px, 1px, 3px, 0px);
badgeShift: point(9px, 0px);
}
// Gift.
premiumGiftRowHeight: 56px;
premiumGiftRowBorderWidth: 2px;
premiumGiftRowBorderRadius: 9px;
premiumGiftRowPaddings: margins(19px, 2px, 17px, 2px);
premiumGiftRowMargins: margins(14px, 0px, 15px, 0px);
premiumGiftRowSubtitleTop: 7px;
premiumGiftRowTextLeft: 53px;
premiumGiftRowBadgeHeight: 18px;
premiumGiftRowBadgeRadius: 4px;
premiumGiftRowBadgeMargins: margins(5px, 1px, 5px, 0px);
premiumGiftOption: PremiumOption {
rowPadding: margins(19px, 2px, 17px, 2px);
rowMargins: margins(14px, 0px, 15px, 0px);
rowHeight: 56px;
borderWidth: 2px;
borderRadius: 9px;
subtitleTop: 7px;
textLeft: 53px;
badgeHeight: 18px;
badgeRadius: 4px;
badgeMargins: margins(5px, 1px, 5px, 0px);
}
premiumGiftUserpicPadding: margins(10px, 27px, 18px, 13px);
premiumGiftTitlePadding: margins(18px, 0px, 18px, 0px);

View file

@ -938,7 +938,9 @@ void ShowListBox(
void AddGiftOptions(
not_null<Ui::VerticalLayout*> parent,
std::shared_ptr<Ui::RadiobuttonGroup> group,
std::vector<Data::SubscriptionOption> gifts) {
std::vector<Data::SubscriptionOption> gifts,
const style::PremiumOption &st,
bool topBadges) {
struct Edges {
Ui::RpWidget *top = nullptr;
@ -956,8 +958,8 @@ void AddGiftOptions(
const auto addRow = [&](const Data::SubscriptionOption &info, int index) {
const auto row = parent->add(
object_ptr<Ui::AbstractButton>(parent),
st::premiumGiftRowPaddings);
row->resize(row->width(), st::premiumGiftRowHeight);
st.rowPadding);
row->resize(row->width(), st.rowHeight);
{
if (!index) {
edges->top = row;
@ -981,7 +983,7 @@ void AddGiftOptions(
- margins.top()
- margins.bottom();
radio->moveToLeft(
st::premiumGiftRowMargins.left(),
st.rowMargins.left(),
(s.height() - radioHeight) / 2);
}, radio->lifetime());
@ -992,30 +994,45 @@ void AddGiftOptions(
p.fillRect(r, Qt::transparent);
const auto left = st::premiumGiftRowTextLeft;
const auto borderWidth = st::premiumGiftRowBorderWidth;
const auto left = st.textLeft;
const auto halfHeight = row->height() / 2;
const auto titleFont = st::semiboldFont;
p.setFont(titleFont);
p.setPen(st::boxTextFg);
p.drawText(
left,
st::premiumGiftRowSubtitleTop + titleFont->ascent,
info.duration);
if (info.costPerMonth.isEmpty() && info.discount.isEmpty()) {
const auto r = row->rect().translated(
-row->rect().left() + left,
0);
p.drawText(r, info.duration, style::al_left);
} else {
p.drawText(
left,
st.subtitleTop + titleFont->ascent,
info.duration);
}
const auto discountFont = st::windowFiltersButton.badgeStyle.font;
const auto discountWidth = discountFont->width(info.discount);
const auto &discountMargins = discountWidth
? st::premiumGiftRowBadgeMargins
? st.badgeMargins
: style::margins();
const auto discountRect = QRect(
const auto bottomLeftRect = QRect(
left,
halfHeight + discountMargins.top(),
discountWidth
+ discountMargins.left()
+ discountMargins.right(),
st::premiumGiftRowBadgeHeight);
st.badgeHeight);
const auto discountRect = topBadges
? bottomLeftRect.translated(
titleFont->width(info.duration) + st.badgeShift.x(),
-bottomLeftRect.top()
+ st.badgeShift.y()
+ st.subtitleTop
+ (titleFont->height - bottomLeftRect.height()) / 2)
: bottomLeftRect;
{
const auto from = edges->top->y();
const auto to = edges->bottom->y() + edges->bottom->height();
@ -1023,17 +1040,17 @@ void AddGiftOptions(
p.setPen(Qt::NoPen);
p.setBrush(partialGradient.compute(row->y(), row->height()));
const auto round = st::premiumGiftRowBadgeRadius;
const auto round = st.badgeRadius;
p.drawRoundedRect(discountRect, round, round);
}
if (animation->nowIndex == index) {
if (st.borderWidth && (animation->nowIndex == index)) {
const auto progress = animation->animation.value(1.);
const auto w = row->width();
auto gradient = QLinearGradient(w - w * progress, 0, w * 2, 0);
gradient.setSpread(QGradient::Spread::RepeatSpread);
gradient.setStops(stops);
const auto pen = QPen(QBrush(gradient), borderWidth);
const auto pen = QPen(QBrush(gradient), st.borderWidth);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
const auto borderRect = row->rect()
@ -1042,7 +1059,7 @@ void AddGiftOptions(
pen.width() / 2,
pen.width() / 2,
pen.width() / 2);
const auto round = st::premiumGiftRowBorderRadius;
const auto round = st.borderRadius;
p.drawRoundedRect(borderRect, round, round);
}
@ -1051,15 +1068,17 @@ void AddGiftOptions(
p.drawText(discountRect, info.discount, style::al_center);
const auto perRect = QMargins(0, 0, row->width(), 0)
+ discountRect.translated(
discountRect.width() + discountMargins.left(),
+ bottomLeftRect.translated(
topBadges
? 0
: bottomLeftRect.width() + discountMargins.left(),
0);
p.setPen(st::windowSubTextFg);
p.setFont(st::shareBoxListItem.nameStyle.font);
p.drawText(perRect, info.costPerMonth, style::al_left);
const auto totalRect = row->rect()
- QMargins(0, 0, st::premiumGiftRowMargins.right(), 0);
- QMargins(0, 0, st.rowMargins.right(), 0);
p.setFont(st::normalFont);
p.drawText(totalRect, info.costTotal, style::al_right);
}, row->lifetime());

View file

@ -22,6 +22,7 @@ struct SubscriptionOption;
namespace style {
struct RoundImageCheckbox;
struct PremiumOption;
struct TextStyle;
} // namespace style
@ -90,7 +91,9 @@ void ShowListBox(
void AddGiftOptions(
not_null<Ui::VerticalLayout*> parent,
std::shared_ptr<Ui::RadiobuttonGroup> group,
std::vector<Data::SubscriptionOption> gifts);
std::vector<Data::SubscriptionOption> gifts,
const style::PremiumOption &st,
bool topBadges = false);
} // namespace Premium
} // namespace Ui