From de31c1cf0c8716b92155607f9003d3723206476c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 31 May 2022 21:11:59 +0400 Subject: [PATCH] Subscription status display. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/api/api_premium.cpp | 58 +++++++++ Telegram/SourceFiles/api/api_premium.h | 37 ++++++ Telegram/SourceFiles/apiwrap.cpp | 8 +- Telegram/SourceFiles/apiwrap.h | 3 + .../SourceFiles/info/info_content_widget.h | 6 +- .../SourceFiles/info/info_layer_widget.cpp | 14 ++- .../SourceFiles/info/info_wrap_widget.cpp | 4 + Telegram/SourceFiles/info/info_wrap_widget.h | 2 + .../info/settings/info_settings_widget.cpp | 4 + .../info/settings/info_settings_widget.h | 1 + Telegram/SourceFiles/settings/settings.style | 1 + .../SourceFiles/settings/settings_common.h | 3 + .../SourceFiles/settings/settings_main.cpp | 4 + .../SourceFiles/settings/settings_premium.cpp | 115 ++++++++++++------ Telegram/lib_ui | 2 +- 16 files changed, 221 insertions(+), 43 deletions(-) create mode 100644 Telegram/SourceFiles/api/api_premium.cpp create mode 100644 Telegram/SourceFiles/api/api_premium.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 5b38134e1..d258b99aa 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -137,6 +137,8 @@ PRIVATE api/api_peer_photo.h api/api_polls.cpp api/api_polls.h + api/api_premium.cpp + api/api_premium.h api/api_report.cpp api/api_report.h api/api_ringtones.cpp diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp new file mode 100644 index 000000000..01579371d --- /dev/null +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -0,0 +1,58 @@ +/* +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 "api/api_premium.h" + +#include "api/api_text_entities.h" +#include "main/main_session.h" +#include "data/data_peer_values.h" +#include "data/data_session.h" +#include "data/data_peer.h" +#include "apiwrap.h" + +namespace Api { + +Premium::Premium(not_null api) +: _session(&api->session()) +, _api(&api->instance()) { + crl::on_main(_session, [=] { + // You can't use _session->user() in the constructor, + // only queued, because it is not constructed yet. + Data::AmPremiumValue( + _session + ) | rpl::skip(1) | rpl::start_with_next([=] { + reload(); + }, _session->lifetime()); + }); +} + +rpl::producer Premium::statusTextValue() const { + return _statusTextUpdates.events_starting_with_copy( + _statusText.value_or(TextWithEntities())); +} + +void Premium::reload() { + if (_statusRequestId) { + return; + } + _statusRequestId = _api.request(MTPhelp_GetPremiumPromo( + )).done([=](const MTPhelp_PremiumPromo &result) { + _statusRequestId = 0; + result.match([&](const MTPDhelp_premiumPromo &data) { + auto text = TextWithEntities{ + qs(data.vstatus_text()), + EntitiesFromMTP(_session, data.vstatus_entities().v), + }; + _statusText = text; + _statusTextUpdates.fire(std::move(text)); + }); + }).fail([=] { + _statusRequestId = 0; + }).send(); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h new file mode 100644 index 000000000..5a4345d7e --- /dev/null +++ b/Telegram/SourceFiles/api/api_premium.h @@ -0,0 +1,37 @@ +/* +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 ApiWrap; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +class Premium final { +public: + explicit Premium(not_null api); + + void reload(); + [[nodiscard]] rpl::producer statusTextValue() const; + +private: + const not_null _session; + MTP::Sender _api; + + mtpRequestId _statusRequestId = 0; + std::optional _statusText; + rpl::event_stream _statusTextUpdates; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index cc7f77775..173f8682a 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_unread_things.h" #include "api/api_ringtones.h" #include "api/api_transcribes.h" +#include "api/api_premium.h" #include "data/notify/data_notify_settings.h" #include "data/stickers/data_stickers.h" #include "data/data_drafts.h" @@ -147,7 +148,8 @@ ApiWrap::ApiWrap(not_null session) , _chatParticipants(std::make_unique(this)) , _unreadThings(std::make_unique(this)) , _ringtones(std::make_unique(this)) -, _transcribes(std::make_unique(this)) { +, _transcribes(std::make_unique(this)) +, _premium(std::make_unique(this)) { crl::on_main(session, [=] { // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. @@ -4111,3 +4113,7 @@ Api::Ringtones &ApiWrap::ringtones() { Api::Transcribes &ApiWrap::transcribes() { return *_transcribes; } + +Api::Premium &ApiWrap::premium() { + return *_premium; +} diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 24d795c2b..707d0790a 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -71,6 +71,7 @@ class ChatParticipants; class UnreadThings; class Ringtones; class Transcribes; +class Premium; namespace details { @@ -363,6 +364,7 @@ public: [[nodiscard]] Api::UnreadThings &unreadThings(); [[nodiscard]] Api::Ringtones &ringtones(); [[nodiscard]] Api::Transcribes &transcribes(); + [[nodiscard]] Api::Premium &premium(); void updatePrivacyLastSeens(); @@ -647,6 +649,7 @@ private: const std::unique_ptr _unreadThings; const std::unique_ptr _ringtones; const std::unique_ptr _transcribes; + const std::unique_ptr _premium; mtpRequestId _wallPaperRequestId = 0; QString _wallPaperSlug; diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index 55caddea7..eae8e5431 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -16,6 +16,7 @@ enum class SharedMediaType : signed char; } // namespace Storage namespace Ui { +class RoundRect; class ScrollArea; class InputField; struct ScrollToRequest; @@ -67,7 +68,10 @@ public: int topDelta); void applyAdditionalScroll(int additionalScroll); int scrollTillBottom(int forHeight) const; - rpl::producer scrollTillBottomChanges() const; + [[nodiscard]] rpl::producer scrollTillBottomChanges() const; + [[nodiscard]] virtual const Ui::RoundRect *bottomSkipRounding() const { + return nullptr; + } // Float player interface. bool floatPlayerHandleWheelEvent(QEvent *e); diff --git a/Telegram/SourceFiles/info/info_layer_widget.cpp b/Telegram/SourceFiles/info/info_layer_widget.cpp index d12c2bda7..63de91226 100644 --- a/Telegram/SourceFiles/info/info_layer_widget.cpp +++ b/Telegram/SourceFiles/info/info_layer_widget.cpp @@ -306,7 +306,6 @@ QRect LayerWidget::countGeometry(int newWidth) { if (_contentTillBottom) { contentHeight += contentBottom; } - const auto bottomPadding = _tillBottom ? 0 : contentBottom; _content->updateGeometry({ contentLeft, contentTop, @@ -330,11 +329,18 @@ void LayerWidget::paintEvent(QPaintEvent *e) { const auto radius = st::boxRadius; auto parts = RectPart::None | 0; if (!_tillBottom) { - if (clip.intersects({ 0, height() - radius, width(), radius })) { - parts |= RectPart::FullBottom; + const auto bottom = QRect{ 0, height() - radius, width(), radius }; + if (clip.intersects(bottom)) { + if (const auto rounding = _content->bottomSkipRounding()) { + rounding->paint(p, rect(), RectPart::FullBottom); + } else { + parts |= RectPart::FullBottom; + } } } else if (!_contentTillBottom) { - p.fillRect(0, height() - radius, width(), radius, st::boxBg); + const auto rounding = _content->bottomSkipRounding(); + const auto &color = rounding ? rounding->color() : st::boxBg; + p.fillRect(0, height() - radius, width(), radius, color); } if (_content->animatingShow()) { if (clip.intersects({ 0, 0, width(), radius })) { diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index cae3ed40c..1e82be2ca 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -1156,6 +1156,10 @@ rpl::producer WrapWidget::grabbingForExpanding() const { return _grabbingForExpanding.value(); } +const Ui::RoundRect *WrapWidget::bottomSkipRounding() const { + return _content->bottomSkipRounding(); +} + bool WrapWidget::hasBackButton() const { return (wrap() == Wrap::Narrow || hasStackHistory()); } diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h index 2b248e7d1..b3d22e5e0 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.h +++ b/Telegram/SourceFiles/info/info_wrap_widget.h @@ -20,6 +20,7 @@ class FadeShadow; class PlainShadow; class PopupMenu; class IconButton; +class RoundRect; } // namespace Ui namespace Window { @@ -129,6 +130,7 @@ public: [[nodiscard]] int scrollTillBottom(int forHeight) const; [[nodiscard]] rpl::producer scrollTillBottomChanges() const; [[nodiscard]] rpl::producer grabbingForExpanding() const; + [[nodiscard]] const Ui::RoundRect *bottomSkipRounding() const; ~WrapWidget(); diff --git a/Telegram/SourceFiles/info/settings/info_settings_widget.cpp b/Telegram/SourceFiles/info/settings/info_settings_widget.cpp index 3c79f5e02..ca294ceb5 100644 --- a/Telegram/SourceFiles/info/settings/info_settings_widget.cpp +++ b/Telegram/SourceFiles/info/settings/info_settings_widget.cpp @@ -213,6 +213,10 @@ void Widget::setInnerFocus() { _inner->setInnerFocus(); } +const Ui::RoundRect *Widget::bottomSkipRounding() const { + return _inner->bottomSkipRounding(); +} + rpl::producer Widget::desiredShadowVisibility() const { return (_type == ::Settings::Main::Id() || _type == ::Settings::Information::Id()) diff --git a/Telegram/SourceFiles/info/settings/info_settings_widget.h b/Telegram/SourceFiles/info/settings/info_settings_widget.h index 7f964e585..e6715d68c 100644 --- a/Telegram/SourceFiles/info/settings/info_settings_widget.h +++ b/Telegram/SourceFiles/info/settings/info_settings_widget.h @@ -72,6 +72,7 @@ public: void showFinished() override; void setInnerFocus() override; + const Ui::RoundRect *bottomSkipRounding() const override; rpl::producer desiredShadowVisibility() const override; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 6e97543a7..4b50a7a21 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -455,3 +455,4 @@ settingsPremiumAboutTextStyle: TextStyle(defaultTextStyle) { font: font(12px); lineHeight: 18px; } +settingsPremiumStatusPadding: margins(22px, 8px, 22px, 2px); diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index 48589fafb..a9d09f7b7 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -93,6 +93,9 @@ public: virtual void setInnerFocus() { setFocus(); } + [[nodiscard]] virtual const Ui::RoundRect *bottomSkipRounding() const { + return nullptr; + } [[nodiscard]] virtual QPointer createPinnedToTop( not_null parent) { return nullptr; diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 3d44964a7..cf9fd4e6b 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_cloud_password.h" #include "api/api_global_privacy.h" #include "api/api_sensitive_content.h" +#include "api/api_premium.h" #include "info/profile/info_profile_values.h" #include "window/window_controller.h" #include "window/window_session_controller.h" @@ -558,6 +559,9 @@ Main::Main( : Section(parent) , _controller(controller) { setupContent(controller); + if (_controller->session().premium()) { + _controller->session().api().premium().reload(); + } } rpl::producer Main::title() { diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 569cd7fa4..aa25bcc3d 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "base/unixtime.h" #include "apiwrap.h" +#include "api/api_premium.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" #include "styles/style_info.h" @@ -164,6 +165,43 @@ using Order = std::vector; }; } +[[nodiscard]] not_null CreateSubscribeButton( + not_null parent, + Fn callback) { + const auto result = Ui::CreateChild( + parent.get(), + Ui::Premium::ButtonGradientStops()); + + result->setClickedCallback(std::move(callback)); + + const auto &st = st::premiumPreviewBox.button; + result->resize(parent->width(), st.height); + + const auto label = Ui::CreateChild( + result, + tr::lng_premium_summary_button(tr::now, lt_cost, "$5"), + st::premiumPreviewButtonLabel); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + rpl::combine( + result->widthValue(), + label->widthValue() + ) | rpl::start_with_next([=](int outer, int width) { + label->moveToLeft( + (outer - width) / 2, + st::premiumPreviewBox.button.textTop, + outer); + }, label->lifetime()); + + parent->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto padding = st::settingsPremiumButtonPadding; + result->resizeToWidth(width - padding.left() - padding.right()); + result->moveToLeft(padding.left(), padding.top(), width); + }, result->lifetime()); + + return result; +} + void SendAppLog( not_null session, const QString &type, @@ -583,6 +621,7 @@ public: not_null parent) override; [[nodiscard]] bool hasFlexibleTopBar() const override; + [[nodiscard]] const Ui::RoundRect *bottomSkipRounding() const override; void setStepDataReference(std::any &data) override; @@ -598,6 +637,7 @@ private: base::unique_qptr _close; rpl::variable _backToggles; rpl::variable _wrap; + std::optional _bottomSkipRounding; rpl::event_stream<> _showBack; @@ -610,6 +650,7 @@ Premium::Premium( , _controller(controller) , _ref(ResolveRef(controller->premiumRef())) { setupContent(); + _controller->session().api().premium().reload(); } rpl::producer Premium::title() { @@ -620,6 +661,10 @@ bool Premium::hasFlexibleTopBar() const { return true; } +const Ui::RoundRect *Premium::bottomSkipRounding() const { + return _bottomSkipRounding ? &*_bottomSkipRounding : nullptr; +} + rpl::producer<> Premium::sectionShowBack() { return _showBack.events(); } @@ -763,8 +808,6 @@ void Premium::setupContent() { st::aboutLabel), st::boxRowPadding); AddSkip(content, stDefault.padding.top() + stDefault.padding.bottom()); -#else - AddSkip(content, stDefault.padding.bottom()); #endif Ui::ResizeFitChild(this, content); @@ -835,51 +878,51 @@ QPointer Premium::createPinnedToBottom( not_null parent) { const auto content = Ui::CreateChild(parent.get()); - auto result = object_ptr( - content, - Ui::Premium::ButtonGradientStops()); - const auto raw = result.data(); - - raw->setClickedCallback([=] { + const auto button = CreateSubscribeButton(content, [=] { SendScreenAccept(_controller); StartPremiumPayment(_controller, _ref); }); - - const auto &st = st::premiumPreviewBox.button; - raw->resize(content->width(), st.height); - - const auto label = Ui::CreateChild( - raw, - tr::lng_premium_summary_button(tr::now, lt_cost, "$5"), - st::premiumPreviewButtonLabel); - label->setAttribute(Qt::WA_TransparentForMouseEvents); - rpl::combine( - raw->widthValue(), - label->widthValue() - ) | rpl::start_with_next([=](int outer, int width) { - label->moveToLeft( - (outer - width) / 2, - st::premiumPreviewBox.button.textTop, - outer); - }, label->lifetime()); - + auto text = _controller->session().api().premium().statusTextValue(); + const auto status = Ui::CreateChild( + content, + object_ptr( + content, + rpl::duplicate(text), + st::boxDividerLabel), + st::settingsPremiumStatusPadding, + RectPart::Top); content->widthValue( ) | rpl::start_with_next([=](int width) { - const auto padding = st::settingsPremiumButtonPadding; - raw->resizeToWidth(width - padding.left() - padding.right()); - }, raw->lifetime()); + status->resizeToWidth(width); + }, status->lifetime()); rpl::combine( - raw->heightValue(), + button->heightValue(), + status->heightValue(), + std::move(text), Data::AmPremiumValue(&_controller->session()) - ) | rpl::start_with_next([=](int height, bool premium) { + ) | rpl::start_with_next([=]( + int buttonHeight, + int statusHeight, + const TextWithEntities &text, + bool premium) { const auto padding = st::settingsPremiumButtonPadding; - const auto finalHeight = premium + const auto finalHeight = !premium + ? (padding.top() + buttonHeight + padding.bottom()) + : text.text.isEmpty() ? 0 - : (padding.top() + height + padding.bottom()); + : statusHeight; content->resize(content->width(), finalHeight); - raw->moveToLeft(padding.left(), padding.top()); - }, raw->lifetime()); + button->moveToLeft(padding.left(), padding.top()); + status->moveToLeft(0, 0); + button->setVisible(!premium); + status->setVisible(premium && !text.text.isEmpty()); + if (!premium || text.text.isEmpty()) { + _bottomSkipRounding.reset(); + } else if (!_bottomSkipRounding) { + _bottomSkipRounding.emplace(st::boxRadius, st::boxDividerBg); + } + }, button->lifetime()); return Ui::MakeWeak(not_null{ content }); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 094c2ea96..b802516ca 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 094c2ea9669dbcb9547b7868d9de0e00fc04844d +Subproject commit b802516ca73c9b20103b5e98d940490dfcc7fea8