Subscription status display.

This commit is contained in:
John Preston 2022-05-31 21:11:59 +04:00
parent 1d7e901b7a
commit de31c1cf0c
16 changed files with 221 additions and 43 deletions

View file

@ -137,6 +137,8 @@ PRIVATE
api/api_peer_photo.h api/api_peer_photo.h
api/api_polls.cpp api/api_polls.cpp
api/api_polls.h api/api_polls.h
api/api_premium.cpp
api/api_premium.h
api/api_report.cpp api/api_report.cpp
api/api_report.h api/api_report.h
api/api_ringtones.cpp api/api_ringtones.cpp

View file

@ -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<ApiWrap*> 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<TextWithEntities> 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

View file

@ -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<ApiWrap*> api);
void reload();
[[nodiscard]] rpl::producer<TextWithEntities> statusTextValue() const;
private:
const not_null<Main::Session*> _session;
MTP::Sender _api;
mtpRequestId _statusRequestId = 0;
std::optional<TextWithEntities> _statusText;
rpl::event_stream<TextWithEntities> _statusTextUpdates;
};
} // namespace Api

View file

@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_unread_things.h" #include "api/api_unread_things.h"
#include "api/api_ringtones.h" #include "api/api_ringtones.h"
#include "api/api_transcribes.h" #include "api/api_transcribes.h"
#include "api/api_premium.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/data_drafts.h" #include "data/data_drafts.h"
@ -147,7 +148,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
, _chatParticipants(std::make_unique<Api::ChatParticipants>(this)) , _chatParticipants(std::make_unique<Api::ChatParticipants>(this))
, _unreadThings(std::make_unique<Api::UnreadThings>(this)) , _unreadThings(std::make_unique<Api::UnreadThings>(this))
, _ringtones(std::make_unique<Api::Ringtones>(this)) , _ringtones(std::make_unique<Api::Ringtones>(this))
, _transcribes(std::make_unique<Api::Transcribes>(this)) { , _transcribes(std::make_unique<Api::Transcribes>(this))
, _premium(std::make_unique<Api::Premium>(this)) {
crl::on_main(session, [=] { crl::on_main(session, [=] {
// You can't use _session->lifetime() in the constructor, // You can't use _session->lifetime() in the constructor,
// only queued, because it is not constructed yet. // only queued, because it is not constructed yet.
@ -4111,3 +4113,7 @@ Api::Ringtones &ApiWrap::ringtones() {
Api::Transcribes &ApiWrap::transcribes() { Api::Transcribes &ApiWrap::transcribes() {
return *_transcribes; return *_transcribes;
} }
Api::Premium &ApiWrap::premium() {
return *_premium;
}

View file

@ -71,6 +71,7 @@ class ChatParticipants;
class UnreadThings; class UnreadThings;
class Ringtones; class Ringtones;
class Transcribes; class Transcribes;
class Premium;
namespace details { namespace details {
@ -363,6 +364,7 @@ public:
[[nodiscard]] Api::UnreadThings &unreadThings(); [[nodiscard]] Api::UnreadThings &unreadThings();
[[nodiscard]] Api::Ringtones &ringtones(); [[nodiscard]] Api::Ringtones &ringtones();
[[nodiscard]] Api::Transcribes &transcribes(); [[nodiscard]] Api::Transcribes &transcribes();
[[nodiscard]] Api::Premium &premium();
void updatePrivacyLastSeens(); void updatePrivacyLastSeens();
@ -647,6 +649,7 @@ private:
const std::unique_ptr<Api::UnreadThings> _unreadThings; const std::unique_ptr<Api::UnreadThings> _unreadThings;
const std::unique_ptr<Api::Ringtones> _ringtones; const std::unique_ptr<Api::Ringtones> _ringtones;
const std::unique_ptr<Api::Transcribes> _transcribes; const std::unique_ptr<Api::Transcribes> _transcribes;
const std::unique_ptr<Api::Premium> _premium;
mtpRequestId _wallPaperRequestId = 0; mtpRequestId _wallPaperRequestId = 0;
QString _wallPaperSlug; QString _wallPaperSlug;

View file

@ -16,6 +16,7 @@ enum class SharedMediaType : signed char;
} // namespace Storage } // namespace Storage
namespace Ui { namespace Ui {
class RoundRect;
class ScrollArea; class ScrollArea;
class InputField; class InputField;
struct ScrollToRequest; struct ScrollToRequest;
@ -67,7 +68,10 @@ public:
int topDelta); int topDelta);
void applyAdditionalScroll(int additionalScroll); void applyAdditionalScroll(int additionalScroll);
int scrollTillBottom(int forHeight) const; int scrollTillBottom(int forHeight) const;
rpl::producer<int> scrollTillBottomChanges() const; [[nodiscard]] rpl::producer<int> scrollTillBottomChanges() const;
[[nodiscard]] virtual const Ui::RoundRect *bottomSkipRounding() const {
return nullptr;
}
// Float player interface. // Float player interface.
bool floatPlayerHandleWheelEvent(QEvent *e); bool floatPlayerHandleWheelEvent(QEvent *e);

View file

@ -306,7 +306,6 @@ QRect LayerWidget::countGeometry(int newWidth) {
if (_contentTillBottom) { if (_contentTillBottom) {
contentHeight += contentBottom; contentHeight += contentBottom;
} }
const auto bottomPadding = _tillBottom ? 0 : contentBottom;
_content->updateGeometry({ _content->updateGeometry({
contentLeft, contentLeft,
contentTop, contentTop,
@ -330,11 +329,18 @@ void LayerWidget::paintEvent(QPaintEvent *e) {
const auto radius = st::boxRadius; const auto radius = st::boxRadius;
auto parts = RectPart::None | 0; auto parts = RectPart::None | 0;
if (!_tillBottom) { if (!_tillBottom) {
if (clip.intersects({ 0, height() - radius, width(), radius })) { const auto bottom = QRect{ 0, height() - radius, width(), radius };
parts |= RectPart::FullBottom; if (clip.intersects(bottom)) {
if (const auto rounding = _content->bottomSkipRounding()) {
rounding->paint(p, rect(), RectPart::FullBottom);
} else {
parts |= RectPart::FullBottom;
}
} }
} else if (!_contentTillBottom) { } 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 (_content->animatingShow()) {
if (clip.intersects({ 0, 0, width(), radius })) { if (clip.intersects({ 0, 0, width(), radius })) {

View file

@ -1156,6 +1156,10 @@ rpl::producer<bool> WrapWidget::grabbingForExpanding() const {
return _grabbingForExpanding.value(); return _grabbingForExpanding.value();
} }
const Ui::RoundRect *WrapWidget::bottomSkipRounding() const {
return _content->bottomSkipRounding();
}
bool WrapWidget::hasBackButton() const { bool WrapWidget::hasBackButton() const {
return (wrap() == Wrap::Narrow || hasStackHistory()); return (wrap() == Wrap::Narrow || hasStackHistory());
} }

View file

@ -20,6 +20,7 @@ class FadeShadow;
class PlainShadow; class PlainShadow;
class PopupMenu; class PopupMenu;
class IconButton; class IconButton;
class RoundRect;
} // namespace Ui } // namespace Ui
namespace Window { namespace Window {
@ -129,6 +130,7 @@ public:
[[nodiscard]] int scrollTillBottom(int forHeight) const; [[nodiscard]] int scrollTillBottom(int forHeight) const;
[[nodiscard]] rpl::producer<int> scrollTillBottomChanges() const; [[nodiscard]] rpl::producer<int> scrollTillBottomChanges() const;
[[nodiscard]] rpl::producer<bool> grabbingForExpanding() const; [[nodiscard]] rpl::producer<bool> grabbingForExpanding() const;
[[nodiscard]] const Ui::RoundRect *bottomSkipRounding() const;
~WrapWidget(); ~WrapWidget();

View file

@ -213,6 +213,10 @@ void Widget::setInnerFocus() {
_inner->setInnerFocus(); _inner->setInnerFocus();
} }
const Ui::RoundRect *Widget::bottomSkipRounding() const {
return _inner->bottomSkipRounding();
}
rpl::producer<bool> Widget::desiredShadowVisibility() const { rpl::producer<bool> Widget::desiredShadowVisibility() const {
return (_type == ::Settings::Main::Id() return (_type == ::Settings::Main::Id()
|| _type == ::Settings::Information::Id()) || _type == ::Settings::Information::Id())

View file

@ -72,6 +72,7 @@ public:
void showFinished() override; void showFinished() override;
void setInnerFocus() override; void setInnerFocus() override;
const Ui::RoundRect *bottomSkipRounding() const override;
rpl::producer<bool> desiredShadowVisibility() const override; rpl::producer<bool> desiredShadowVisibility() const override;

View file

@ -455,3 +455,4 @@ settingsPremiumAboutTextStyle: TextStyle(defaultTextStyle) {
font: font(12px); font: font(12px);
lineHeight: 18px; lineHeight: 18px;
} }
settingsPremiumStatusPadding: margins(22px, 8px, 22px, 2px);

View file

@ -93,6 +93,9 @@ public:
virtual void setInnerFocus() { virtual void setInnerFocus() {
setFocus(); setFocus();
} }
[[nodiscard]] virtual const Ui::RoundRect *bottomSkipRounding() const {
return nullptr;
}
[[nodiscard]] virtual QPointer<Ui::RpWidget> createPinnedToTop( [[nodiscard]] virtual QPointer<Ui::RpWidget> createPinnedToTop(
not_null<QWidget*> parent) { not_null<QWidget*> parent) {
return nullptr; return nullptr;

View file

@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_cloud_password.h" #include "api/api_cloud_password.h"
#include "api/api_global_privacy.h" #include "api/api_global_privacy.h"
#include "api/api_sensitive_content.h" #include "api/api_sensitive_content.h"
#include "api/api_premium.h"
#include "info/profile/info_profile_values.h" #include "info/profile/info_profile_values.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
@ -558,6 +559,9 @@ Main::Main(
: Section(parent) : Section(parent)
, _controller(controller) { , _controller(controller) {
setupContent(controller); setupContent(controller);
if (_controller->session().premium()) {
_controller->session().api().premium().reload();
}
} }
rpl::producer<QString> Main::title() { rpl::producer<QString> Main::title() {

View file

@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_premium.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_info.h" #include "styles/style_info.h"
@ -164,6 +165,43 @@ using Order = std::vector<QString>;
}; };
} }
[[nodiscard]] not_null<Ui::RpWidget*> CreateSubscribeButton(
not_null<Ui::RpWidget*> parent,
Fn<void()> callback) {
const auto result = Ui::CreateChild<Ui::GradientButton>(
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<Ui::FlatLabel>(
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( void SendAppLog(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const QString &type, const QString &type,
@ -583,6 +621,7 @@ public:
not_null<Ui::RpWidget*> parent) override; not_null<Ui::RpWidget*> parent) override;
[[nodiscard]] bool hasFlexibleTopBar() const override; [[nodiscard]] bool hasFlexibleTopBar() const override;
[[nodiscard]] const Ui::RoundRect *bottomSkipRounding() const override;
void setStepDataReference(std::any &data) override; void setStepDataReference(std::any &data) override;
@ -598,6 +637,7 @@ private:
base::unique_qptr<Ui::IconButton> _close; base::unique_qptr<Ui::IconButton> _close;
rpl::variable<bool> _backToggles; rpl::variable<bool> _backToggles;
rpl::variable<Info::Wrap> _wrap; rpl::variable<Info::Wrap> _wrap;
std::optional<Ui::RoundRect> _bottomSkipRounding;
rpl::event_stream<> _showBack; rpl::event_stream<> _showBack;
@ -610,6 +650,7 @@ Premium::Premium(
, _controller(controller) , _controller(controller)
, _ref(ResolveRef(controller->premiumRef())) { , _ref(ResolveRef(controller->premiumRef())) {
setupContent(); setupContent();
_controller->session().api().premium().reload();
} }
rpl::producer<QString> Premium::title() { rpl::producer<QString> Premium::title() {
@ -620,6 +661,10 @@ bool Premium::hasFlexibleTopBar() const {
return true; return true;
} }
const Ui::RoundRect *Premium::bottomSkipRounding() const {
return _bottomSkipRounding ? &*_bottomSkipRounding : nullptr;
}
rpl::producer<> Premium::sectionShowBack() { rpl::producer<> Premium::sectionShowBack() {
return _showBack.events(); return _showBack.events();
} }
@ -763,8 +808,6 @@ void Premium::setupContent() {
st::aboutLabel), st::aboutLabel),
st::boxRowPadding); st::boxRowPadding);
AddSkip(content, stDefault.padding.top() + stDefault.padding.bottom()); AddSkip(content, stDefault.padding.top() + stDefault.padding.bottom());
#else
AddSkip(content, stDefault.padding.bottom());
#endif #endif
Ui::ResizeFitChild(this, content); Ui::ResizeFitChild(this, content);
@ -835,51 +878,51 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
not_null<Ui::RpWidget*> parent) { not_null<Ui::RpWidget*> parent) {
const auto content = Ui::CreateChild<Ui::RpWidget>(parent.get()); const auto content = Ui::CreateChild<Ui::RpWidget>(parent.get());
auto result = object_ptr<Ui::GradientButton>( const auto button = CreateSubscribeButton(content, [=] {
content,
Ui::Premium::ButtonGradientStops());
const auto raw = result.data();
raw->setClickedCallback([=] {
SendScreenAccept(_controller); SendScreenAccept(_controller);
StartPremiumPayment(_controller, _ref); StartPremiumPayment(_controller, _ref);
}); });
auto text = _controller->session().api().premium().statusTextValue();
const auto &st = st::premiumPreviewBox.button; const auto status = Ui::CreateChild<Ui::DividerLabel>(
raw->resize(content->width(), st.height); content,
object_ptr<Ui::FlatLabel>(
const auto label = Ui::CreateChild<Ui::FlatLabel>( content,
raw, rpl::duplicate(text),
tr::lng_premium_summary_button(tr::now, lt_cost, "$5"), st::boxDividerLabel),
st::premiumPreviewButtonLabel); st::settingsPremiumStatusPadding,
label->setAttribute(Qt::WA_TransparentForMouseEvents); RectPart::Top);
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());
content->widthValue( content->widthValue(
) | rpl::start_with_next([=](int width) { ) | rpl::start_with_next([=](int width) {
const auto padding = st::settingsPremiumButtonPadding; status->resizeToWidth(width);
raw->resizeToWidth(width - padding.left() - padding.right()); }, status->lifetime());
}, raw->lifetime());
rpl::combine( rpl::combine(
raw->heightValue(), button->heightValue(),
status->heightValue(),
std::move(text),
Data::AmPremiumValue(&_controller->session()) 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 padding = st::settingsPremiumButtonPadding;
const auto finalHeight = premium const auto finalHeight = !premium
? (padding.top() + buttonHeight + padding.bottom())
: text.text.isEmpty()
? 0 ? 0
: (padding.top() + height + padding.bottom()); : statusHeight;
content->resize(content->width(), finalHeight); content->resize(content->width(), finalHeight);
raw->moveToLeft(padding.left(), padding.top()); button->moveToLeft(padding.left(), padding.top());
}, raw->lifetime()); 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<Ui::RpWidget*>{ content }); return Ui::MakeWeak(not_null<Ui::RpWidget*>{ content });
} }

@ -1 +1 @@
Subproject commit 094c2ea9669dbcb9547b7868d9de0e00fc04844d Subproject commit b802516ca73c9b20103b5e98d940490dfcc7fea8