Added initial box for premium gifts.

This commit is contained in:
23rd 2022-07-03 17:56:12 +03:00 committed by John Preston
parent 2a3d72ad2e
commit c7c8ebed13
14 changed files with 407 additions and 5 deletions

View File

@ -240,6 +240,8 @@ PRIVATE
boxes/edit_color_box.h
boxes/edit_privacy_box.cpp
boxes/edit_privacy_box.h
boxes/gift_premium_box.cpp
boxes/gift_premium_box.h
boxes/language_box.cpp
boxes/language_box.h
boxes/local_storage_box.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -167,8 +167,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channels_limit_title" = "Too Many Communities";
"lng_channels_limit1#one" = "You are a member of **{count}** groups and channels.";
"lng_channels_limit1#other" = "You are a member of **{count}** groups and channels.";
"lng_channels_limit2#one" = "Please leave some before joining a new one - or upgrade to **Telegram Premium** to doulbe the limit to **{count}** groups and channels.";
"lng_channels_limit2#other" = "Please leave some before joining a new one - or upgrade to **Telegram Premium** to doulbe the limit to **{count}** groups and channels.";
"lng_channels_limit2#one" = "Please leave some before joining a new one - or upgrade to **Telegram Premium** to double the limit to **{count}** groups and channels.";
"lng_channels_limit2#other" = "Please leave some before joining a new one - or upgrade to **Telegram Premium** to double the limit to **{count}** groups and channels.";
"lng_channels_limit2_final" = "Please leave some before joining a new one.";
"lng_channels_leave_title" = "Least active communities";
"lng_channels_leave_status" = "{type}, inactive {time}";
@ -1140,6 +1140,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_unblock_user" = "Unblock user";
"lng_profile_export_chat" = "Export history";
"lng_profile_export_channel" = "Export history";
"lng_profile_gift_premium" = "Gift Premium";
"lng_media_selected_photo#one" = "{count} Photo";
"lng_media_selected_photo#other" = "{count} Photos";
"lng_media_selected_gif#one" = "{count} GIF";
@ -1446,7 +1447,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_you_theme_disabled" = "You disabled chat theme";
"lng_action_theme_disabled" = "{from} disabled chat theme";
"lng_action_proximity_distance_m#one" = "{count} meter";
"lng_action_proximity_distance_m#other" = "{count} metres";
"lng_action_proximity_distance_m#other" = "{count} meters";
"lng_action_proximity_distance_km#one" = "{count} km";
"lng_action_proximity_distance_km#other" = "{count} km";
"lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot.";
@ -1678,7 +1679,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_subtitle_double_limits" = "Doubled Limits";
"lng_premium_summary_about_double_limits" = "Up to 1000 channels, 20 folders, 10 pins, 20 public links, 4 accounts and more.";
"lng_premium_summary_subtitle_more_upload" = "4Gb Upload Size";
"lng_premium_summary_about_more_upload" = "Increased upload size from 2Gb to 4Gb to per document, unlimited storage overall.";
"lng_premium_summary_about_more_upload" = "Increased upload size from 2Gb to 4Gb per document, unlimited storage overall.";
"lng_premium_summary_subtitle_faster_download" = "Faster Download Speed";
"lng_premium_summary_about_faster_download" = "No more limits on the speed with which media and documents are downloaded.";
"lng_premium_summary_subtitle_voice_to_text" = "Voice-to-Text Conversion";
@ -1742,6 +1743,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_double_limits_about_accounts#other" = "Connect {count} accounts with different mobile numbers";
//
"lng_premium_gift_title" = "Gift Telegram Premium";
"lng_premium_gift_about" = "Let **{user}** enjoy exclusive features of Telegram with **Telegram Premium**.";
"lng_premium_gift_button" = "Gift Subscription for {cost}";
"lng_premium_gift_per" = "{cost} / month";
"lng_premium_gift_terms" = "You can review the list of features and terms of use for Telegram Premium {link}.";
"lng_premium_gift_terms_link" = "here";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";

View File

@ -0,0 +1,298 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/gift_premium_box.h"
#include "api/api_premium.h"
#include "apiwrap.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"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_premium.h"
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
#include "ui/effects/premium_graphics.h"
#include "ui/layers/generic_box.h"
#include "ui/special_buttons.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/wrap/padding_wrap.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_info.h"
#include "styles/style_premium.h"
namespace {
constexpr auto kDiscountDivider = 5.;
struct GiftOption final {
QString url;
Ui::Premium::GiftInfo info;
};
using GiftOptions = std::vector<GiftOption>;
GiftOptions GiftOptionFromTL(
const MTPDuserFull &data,
int monthlyAmount,
QString monthlyCurrency) {
auto result = GiftOptions();
const auto gifts = data.vpremium_gifts();
if (!gifts) {
return result;
}
result.reserve(gifts->v.size());
for (const auto &gift : gifts->v) {
const auto &option = gift.match([](const MTPDpremiumGiftOption &d
) -> const MTPDpremiumGiftOption & {
return d;
});
const auto botUrl = qs(option.vbot_url());
const auto months = option.vmonths().v;
const auto amount = option.vamount().v;
const auto currency = qs(option.vcurrency());
if (monthlyCurrency != currency) {
monthlyAmount = 500; // 5 USD.
}
const auto discount = [&] {
const auto percent = monthlyAmount * months / float64(amount)
- 1.;
return std::round(percent * 100. / kDiscountDivider)
* kDiscountDivider;
}();
auto info = Ui::Premium::GiftInfo{
.duration = Ui::FormatTTL(months * 86400 * 31),
.discount = QString("\u2212%1%").arg(discount),
.perMonth = tr::lng_premium_gift_per(
tr::now,
lt_cost,
Ui::FillAmountAndCurrency(
amount / float64(months),
currency)),
.total = Ui::FillAmountAndCurrency(amount, currency),
};
result.push_back({ .url = botUrl, .info = std::move(info) });
}
return result;
}
void GiftBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<UserData*> user,
GiftOptions options) {
const auto boxWidth = st::boxWideWidth;
box->setWidth(boxWidth);
box->setNoContentMargin(true);
const auto buttonsParent = box->verticalLayout().get();
struct State {
rpl::event_stream<QString> buttonText;
};
const auto state = box->lifetime().make_state<State>();
const auto userpicPadding = st::premiumGiftUserpicPadding;
const auto top = box->addRow(object_ptr<Ui::FixedHeightWidget>(
buttonsParent,
userpicPadding.top()
+ userpicPadding.bottom()
+ st::defaultUserpicButton.size.height()));
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
top,
user,
Ui::UserpicButton::Role::Custom,
st::defaultUserpicButton);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
top->widthValue(
) | rpl::start_with_next([=](int width) {
userpic->moveToLeft(
(width - userpic->width()) / 2,
userpicPadding.top());
}, userpic->lifetime());
const auto close = Ui::CreateChild<Ui::IconButton>(
buttonsParent,
st::infoTopBarClose);
close->setClickedCallback([=] { box->closeBox(); });
buttonsParent->widthValue(
) | rpl::start_with_next([=](int width) {
close->moveToRight(0, 0, width);
}, close->lifetime());
// Header.
const auto &padding = st::premiumGiftAboutPadding;
const auto available = boxWidth - padding.left() - padding.right();
const auto &stTitle = st::premiumPreviewAboutTitle;
auto titleLabel = object_ptr<Ui::FlatLabel>(
box,
tr::lng_premium_gift_title(),
stTitle);
titleLabel->resizeToWidth(available);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
std::move(titleLabel)),
st::premiumGiftTitlePadding);
auto textLabel = object_ptr<Ui::FlatLabel>(
box,
tr::lng_premium_gift_about(
lt_user,
user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::Name
) | rpl::map([=] { return TextWithEntities{ user->firstName }; }),
Ui::Text::RichLangValue),
st::premiumPreviewAbout);
textLabel->setTextColorOverride(stTitle.textFg->c);
textLabel->resizeToWidth(available);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(box, std::move(textLabel)),
padding);
// List.
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
group->setChangedCallback([=](int value) {
Expects(value < options.size() && value >= 0);
auto text = tr::lng_premium_gift_button(
tr::now,
lt_cost,
options[value].info.total);
state->buttonText.fire(std::move(text));
});
Ui::Premium::AddGiftOptions(
buttonsParent,
group,
ranges::views::all(
options
) | ranges::views::transform([](const GiftOption &option) {
return option.info;
}) | ranges::to_vector);
// Footer.
auto terms = object_ptr<Ui::FlatLabel>(
box,
tr::lng_premium_gift_terms(
lt_link,
tr::lng_premium_gift_terms_link(
) | rpl::map([=](const QString &t) {
return Ui::Text::Link(t, 1);
}),
Ui::Text::WithEntities),
st::premiumGiftTerms);
terms->setLink(1, std::make_shared<LambdaClickHandler>([=] {
box->closeBox();
Settings::ShowPremium(&user->session(), QString());
}));
terms->resizeToWidth(available);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(box, std::move(terms)),
st::premiumGiftTermsPadding);
// Button.
const auto &stButton = st::premiumGiftBox;
box->setStyle(stButton);
auto raw = Settings::CreateSubscribeButton({
controller,
box,
[] { return QString("gift"); },
state->buttonText.events(),
Ui::Premium::GiftGradientStops(),
});
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].url);
if (local.isEmpty()) {
return;
}
UrlClickHandler::Open(
local,
QVariant::fromValue(ClickHandlerContext{
.botStartAutoSubmit = true,
.sessionWindow = base::make_weak(controller.get()),
}));
});
box->setShowFinishedCallback([raw = button.data()]{
raw->startGlareAnimation();
});
box->addButton(std::move(button));
group->setValue(0);
Data::PeerPremiumValue(
user
) | rpl::skip(1) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
}
} // namespace
GiftPremiumValidator::GiftPremiumValidator(
not_null<Window::SessionController*> controller)
: _controller(controller)
, _api(&_controller->session().mtp()) {
}
void GiftPremiumValidator::cancel() {
_requestId = 0;
}
void GiftPremiumValidator::showBox(not_null<UserData*> user) {
if (_requestId) {
return;
}
_controller->session().api().premium().reload();
_requestId = _api.request(MTPusers_GetFullUser(
user->inputUser
)).done([=](const MTPusers_UserFull &result) {
if (!_requestId) {
// Canceled.
return;
}
_requestId = 0;
// _controller->api().processFullPeer(peer, result);
const auto &data = result.match([](
const MTPDusers_userFull &d) -> const MTPDusers_userFull & {
return d;
});
_controller->session().data().processUsers(data.vusers());
_controller->session().data().processChats(data.vchats());
const auto &fullUser = data.vfull_user().match(
[](const MTPDuserFull &d) -> const MTPDuserFull & {
return d;
});
auto options = GiftOptionFromTL(
fullUser,
_controller->session().api().premium().monthlyAmount(),
_controller->session().api().premium().monthlyCurrency());
if (!options.empty()) {
_controller->show(
Box(GiftBox, _controller, user, std::move(options)));
}
}).fail([=] {
_requestId = 0;
}).send();
}

View File

@ -0,0 +1,31 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/sender.h"
class UserData;
namespace Window {
class SessionController;
} // namespace Window
class GiftPremiumValidator final {
public:
GiftPremiumValidator(not_null<Window::SessionController*> controller);
void showBox(not_null<UserData*> user);
void cancel();
private:
const not_null<Window::SessionController*> _controller;
MTP::Sender _api;
mtpRequestId _requestId = 0;
};

View File

@ -253,6 +253,10 @@ bool UserData::canAddContact() const {
return canShareThisContact() && !isContact();
}
bool UserData::canReceiveGifts() const {
return flags() & UserDataFlag::CanReceiveGifts;
}
bool UserData::canShareThisContactFast() const {
return !_phone.isEmpty();
}
@ -309,14 +313,19 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(user, pinned->v);
}
const auto canReceiveGifts = (update.vflags().v
& MTPDuserFull::Flag::f_premium_gifts)
&& update.vpremium_gifts();
using Flag = UserDataFlag;
const auto mask = Flag::Blocked
| Flag::HasPhoneCalls
| Flag::PhoneCallsPrivate
| Flag::CanReceiveGifts
| Flag::CanPinMessages;
user->setFlags((user->flags() & ~mask)
| (update.is_phone_calls_private() ? Flag::PhoneCallsPrivate : Flag())
| (update.is_phone_calls_available() ? Flag::HasPhoneCalls : Flag())
| (canReceiveGifts ? Flag::CanReceiveGifts : Flag())
| (update.is_can_pin_message() ? Flag::CanPinMessages : Flag())
| (update.is_blocked() ? Flag::Blocked : Flag()));
user->setIsBlocked(update.is_blocked());

View File

@ -51,6 +51,7 @@ enum class UserDataFlag {
DiscardMinPhoto = (1 << 12),
Self = (1 << 13),
Premium = (1 << 14),
CanReceiveGifts = (1 << 15),
};
inline constexpr bool is_flag_type(UserDataFlag) { return true; };
using UserDataFlags = base::flags<UserDataFlag>;
@ -106,6 +107,8 @@ public:
[[nodiscard]] bool canShareThisContact() const;
[[nodiscard]] bool canAddContact() const;
[[nodiscard]] bool canReceiveGifts() const;
// In Data::Session::processUsers() we check only that.
// When actually trying to share contact we perform
// a full check by canShareThisContact() call.

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
using "ui/basic.style";
using "ui/widgets/widgets.style";
using "settings/settings.style";
// Preview.
premiumPreviewBox: Box(defaultBox) {
@ -96,6 +97,7 @@ premiumAccountsNameTop: 13px;
premiumAccountsPadding: margins(0px, 20px, 0px, 14px);
premiumAccountsHeight: 105px;
// Gift.
premiumGiftRowHeight: 56px;
premiumGiftRowBorderWidth: 2px;
premiumGiftRowBorderRadius: 9px;
@ -107,3 +109,18 @@ premiumGiftRowBadgeHeight: 18px;
premiumGiftRowBadgeRadius: 4px;
premiumGiftRowBadgeMargins: margins(5px, 1px, 5px, 0px);
premiumGiftUserpicPadding: margins(10px, 27px, 18px, 13px);
premiumGiftTitlePadding: margins(18px, 0px, 18px, 0px);
premiumGiftAboutPadding: margins(18px, 5px, 18px, 23px);
premiumGiftTermsPadding: margins(18px, 27px, 18px, 0px);
premiumGiftTerms: FlatLabel(settingLocalPasscodeDescription) {
style: TextStyle(defaultTextStyle) {
font: font(11px);
linkFont: font(11px);
linkFontOver: font(11px underline);
}
}
premiumGiftBox: Box(premiumPreviewBox) {
buttonPadding: margins(12px, 12px, 12px, 12px);
}

View File

@ -87,6 +87,7 @@ menuIconFile: icon {{ "menu/file", menuIconColor }};
menuIconPhoto: icon {{ "menu/image", menuIconColor }};
menuIconAddToFolder: icon {{ "menu/add_to_folder", menuIconColor }};
menuIconLeave: icon {{ "menu/leave", menuIconColor }};
menuIconGiftPremium: icon {{ "menu/gift_premium", menuIconColor }};
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
menuIconTTLAnyTextPosition: point(11px, 22px);

View File

@ -202,6 +202,7 @@ private:
void addNewMembers();
void addDeleteContact();
void addTTLSubmenu(bool addSeparator);
void addGiftPremium();
not_null<SessionController*> _controller;
Dialogs::EntryState _request;
@ -806,6 +807,24 @@ void Filler::addTTLSubmenu(bool addSeparator) {
}
}
void Filler::addGiftPremium() {
const auto user = _peer->asUser();
if (!user
|| user->isInaccessible()
|| user->isSelf()
|| user->isBot()
|| user->isNotificationsUser()
|| !user->canReceiveGifts()
|| user->isRepliesChat()) {
return;
}
const auto navigation = _controller;
_addAction(tr::lng_profile_gift_premium(tr::now), [=] {
navigation->showGiftPremiumBox(user);
}, &st::menuIconGiftPremium);
}
void Filler::fill() {
if (_folder) {
fillArchiveActions();
@ -862,6 +881,7 @@ void Filler::fillProfileActions() {
addNewContact();
addShareContact();
addEditContact();
addGiftPremium();
addBotToGroup();
addNewMembers();
addManageChat();

View File

@ -617,7 +617,8 @@ SessionController::SessionController(
, _activeChatsFilter(session->data().chatsFilters().defaultId())
, _defaultChatTheme(std::make_shared<Ui::ChatTheme>())
, _chatStyle(std::make_unique<Ui::ChatStyle>())
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>()) {
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>())
, _giftPremiumValidator(GiftPremiumValidator(this)) {
init();
_chatStyleTheme = _defaultChatTheme;
@ -748,6 +749,14 @@ void SessionController::showEditPeerBox(PeerData *peer) {
session().api().requestFullPeer(peer);
}
void SessionController::showGiftPremiumBox(UserData *user) {
if (user) {
_giftPremiumValidator.showBox(user);
} else {
_giftPremiumValidator.cancel();
}
}
void SessionController::init() {
if (session().supportMode()) {
initSupportMode();

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/observer.h"
#include "base/weak_ptr.h"
#include "base/timer.h"
#include "boxes/gift_premium_box.h" // GiftPremiumValidator.
#include "data/data_chat_participant_status.h"
#include "dialogs/dialogs_key.h"
#include "ui/layers/layer_widget.h"
@ -354,6 +355,7 @@ public:
Dialogs::RowDescriptor from = {}) const;
void showEditPeerBox(PeerData *peer);
void showGiftPremiumBox(UserData *user);
void enableGifPauseReason(GifPauseReason reason);
void disableGifPauseReason(GifPauseReason reason);
@ -599,6 +601,8 @@ private:
using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory;
std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory;
GiftPremiumValidator _giftPremiumValidator;
QString _premiumRef;
rpl::lifetime _lifetime;