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_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

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_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<Main::Session*> session)
, _chatParticipants(std::make_unique<Api::ChatParticipants>(this))
, _unreadThings(std::make_unique<Api::UnreadThings>(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, [=] {
// 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;
}

View File

@ -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<Api::UnreadThings> _unreadThings;
const std::unique_ptr<Api::Ringtones> _ringtones;
const std::unique_ptr<Api::Transcribes> _transcribes;
const std::unique_ptr<Api::Premium> _premium;
mtpRequestId _wallPaperRequestId = 0;
QString _wallPaperSlug;

View File

@ -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<int> scrollTillBottomChanges() const;
[[nodiscard]] rpl::producer<int> scrollTillBottomChanges() const;
[[nodiscard]] virtual const Ui::RoundRect *bottomSkipRounding() const {
return nullptr;
}
// Float player interface.
bool floatPlayerHandleWheelEvent(QEvent *e);

View File

@ -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 })) {

View File

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

View File

@ -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<int> scrollTillBottomChanges() const;
[[nodiscard]] rpl::producer<bool> grabbingForExpanding() const;
[[nodiscard]] const Ui::RoundRect *bottomSkipRounding() const;
~WrapWidget();

View File

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

View File

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

View File

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

View File

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

View File

@ -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<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(
not_null<Main::Session*> session,
const QString &type,
@ -583,6 +621,7 @@ public:
not_null<Ui::RpWidget*> 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<Ui::IconButton> _close;
rpl::variable<bool> _backToggles;
rpl::variable<Info::Wrap> _wrap;
std::optional<Ui::RoundRect> _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<QString> 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<Ui::RpWidget> Premium::createPinnedToBottom(
not_null<Ui::RpWidget*> parent) {
const auto content = Ui::CreateChild<Ui::RpWidget>(parent.get());
auto result = object_ptr<Ui::GradientButton>(
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<Ui::FlatLabel>(
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<Ui::DividerLabel>(
content,
object_ptr<Ui::FlatLabel>(
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<Ui::RpWidget*>{ content });
}

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