Added initial implementation of TopBar in Premium Settings for user.

This commit is contained in:
23rd 2022-08-23 21:07:00 +03:00 committed by John Preston
parent b519b6bf4c
commit 01b50a8460
4 changed files with 509 additions and 30 deletions

View file

@ -1702,6 +1702,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_unlock_stickers" = "Unlock Premium Stickers";
"lng_premium_unlock_emoji" = "Unlock Animated Emoji";
"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";
"lng_premium_summary_user_title" = "{user} is a subscriber of Telegram Premium.";
"lng_premium_summary_user_about" = "Owners of Telegram Premium accounts have exclusive access to multiple additional features.";
"lng_premium_summary_user_button" = "Learn More";
"lng_premium_summary_title" = "Telegram Premium";
"lng_premium_summary_top_about" = "Go **beyond the limits**, get **exclusive features** and support us by subscribing to **Telegram Premium**.";
"lng_premium_summary_title_subscribed" = "You are all set!";

View file

@ -142,8 +142,10 @@ Widget::Widget(
- _pinnedToTop->minimumHeight();
};
_inner->heightValue(
) | rpl::start_with_next([=](int h) {
rpl::combine(
_pinnedToTop->heightValue(),
_inner->heightValue()
) | rpl::start_with_next([=](int, int h) {
_flexibleScroll.contentHeightValue.fire(h + heightDiff());
}, _pinnedToTop->lifetime());

View file

@ -428,8 +428,8 @@ notifyPreviewBottomSkip: 9px;
settingsPremiumButtonPadding: margins(11px, 11px, 11px, 3px);
settingsPremiumTopBarBackIcon: icon {{ "info/info_back", premiumButtonFg }};
settingsPremiumTopBarBackIconOver: icon {{ "info/info_back", premiumButtonFg }};
settingsPremiumStarSize: size(77px, 73px);
settingsPremiumStarTopSkip: 42px;
settingsPremiumStarSize: size(84px, 81px);
settingsPremiumStarTopSkip: 37px;
settingsPremiumTopBarBack: IconButton(infoTopBarBack) {
icon: settingsPremiumTopBarBackIcon;
iconOver: settingsPremiumTopBarBackIconOver;
@ -469,13 +469,14 @@ settingsPremiumPreviewAboutPadding: margins(24px, 0px, 24px, 11px);
settingsPremiumPreviewLinePadding: margins(18px, 0px, 18px, 8px);
settingsPremiumTitlePadding: margins(0px, 20px, 0px, 16px);
settingsPremiumAboutTextStyle: TextStyle(defaultTextStyle) {
font: font(12px);
linkFont: font(12px underline);
linkFontOver: font(12px underline);
lineHeight: 18px;
}
settingsPremiumAbout: FlatLabel(defaultFlatLabel) {
style: TextStyle(defaultTextStyle) {
font: font(12px);
linkFont: font(12px underline);
linkFontOver: font(12px underline);
lineHeight: 18px;
}
style: settingsPremiumAboutTextStyle;
palette: TextPalette(defaultTextPalette) {
linkFg: premiumButtonFg;
}
@ -485,5 +486,25 @@ settingsPremiumAbout: FlatLabel(defaultFlatLabel) {
}
settingsPremiumStatusPadding: margins(22px, 8px, 22px, 2px);
settingsPremiumUserHeight: 223px;
settingsPremiumUserTitlePadding: margins(0px, 16px, 0px, 6px);
settingsPremiumUserTitle: FlatLabel(boxTitle) {
style: TextStyle(defaultTextStyle) {
font: boxTitleFont;
linkFont: boxTitleFont;
linkFontOver: font(16px semibold underline);
lineHeight: 14px;
}
minWidth: 300px;
maxHeight: 0px;
align: align(top);
}
settingsPremiumUserAbout: FlatLabel(boxDividerLabel) {
style: settingsPremiumAboutTextStyle;
minWidth: 315px;
maxHeight: 0px;
align: align(top);
}
settingsPremiumLock: icon{{ "emoji/premium_lock", windowActiveTextFg, point(0px, 1px) }};
settingsPremiumLockSkip: 3px;

View file

@ -7,23 +7,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "settings/settings_premium.h"
#include "boxes/premium_preview_box.h"
#include "boxes/sticker_set_box.h"
#include "chat_helpers/stickers_lottie.h" // LottiePlayerFromDocument.
#include "core/application.h"
#include "core/click_handler_types.h"
#include "core/ui_integration.h" // MarkedTextContext.
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h" // SerializeCustomEmojiId.
#include "data/stickers/data_stickers.h"
#include "history/view/media/history_view_sticker.h" // EmojiSize.
#include "info/info_wrap_widget.h" // Info::Wrap.
#include "info/profile/info_profile_values.h"
#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
#include "lang/lang_keys.h"
#include "boxes/premium_preview_box.h"
#include "lottie/lottie_single_player.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "settings/settings_premium.h"
#include "ui/abstract_button.h"
#include "ui/basic_click_handlers.h"
#include "ui/effects/gradient.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars.h"
#include "ui/text/text_utilities.h"
#include "ui/text/format_values.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
@ -31,10 +46,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_controller.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "window/window_session_controller.h"
#include "base/unixtime.h"
#include "apiwrap.h"
@ -54,6 +65,10 @@ using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData;
constexpr auto kBodyAnimationPart = 0.90;
constexpr auto kTitleAdditionalScale = 0.15;
[[nodiscard]] QString Svg() {
return u":/gui/icons/settings/star.svg"_q;
}
namespace Ref {
namespace Gift {
@ -352,6 +367,395 @@ QRectF TopBarAbstract::starRect(
starSize);
};
class EmojiStatusTopBar final {
public:
EmojiStatusTopBar(
not_null<DocumentData*> document,
Fn<void(QRect)> callback,
QSizeF size);
void setCenter(QPointF position);
void paint(QPainter &p);
private:
QPixmap paintedPixmap(const QSize &size) const;
QRectF _rect;
std::shared_ptr<Data::DocumentMedia> _media;
std::unique_ptr<Lottie::SinglePlayer> _lottie;
rpl::lifetime _lifetime;
};
EmojiStatusTopBar::EmojiStatusTopBar(
not_null<DocumentData*> document,
Fn<void(QRect)> callback,
QSizeF size)
: _rect(QPointF(), size) {
const auto sticker = document->sticker();
Assert(sticker != nullptr);
_media = document->createMediaView();
_media->checkStickerLarge();
_media->goodThumbnailWanted();
rpl::single() | rpl::then(
document->owner().session().downloaderTaskFinished()
) | rpl::start_with_next([=] {
if (!_media->loaded()) {
return;
}
_lifetime.destroy();
if (sticker->isLottie()) {
_lottie = ChatHelpers::LottiePlayerFromDocument(
_media.get(),
ChatHelpers::StickerLottieSize::EmojiInteractionReserved7, //
size.toSize(),
Lottie::Quality::High);
_lifetime = _lottie->updates(
) | rpl::start_with_next([=](Lottie::Update update) {
v::match(update.data, [&](const Lottie::Information &) {
callback(_rect.toRect());
}, [&](const Lottie::DisplayFrameRequest &) {
callback(_rect.toRect());
});
});
}
}, _lifetime);
}
void EmojiStatusTopBar::setCenter(QPointF position) {
const auto size = _rect.size();
const auto shift = QPointF(size.width() / 2., size.height() / 2.);
_rect = QRectF(QPointF(position - shift), QPointF(position + shift));
}
QPixmap EmojiStatusTopBar::paintedPixmap(const QSize &size) const {
const auto good = _media->goodThumbnail();
if (const auto image = _media->getStickerLarge()) {
return image->pix(size);
} else if (good) {
return good->pix(size);
} else if (const auto thumbnail = _media->thumbnail()) {
return thumbnail->pix(size, { .options = Images::Option::Blur });
}
return QPixmap();
}
void EmojiStatusTopBar::paint(QPainter &p) {
if (_lottie) {
if (_lottie->ready()) {
const auto info = _lottie->frameInfo({
.box = (_rect.size() * style::DevicePixelRatio()).toSize(),
});
p.drawImage(_rect, info.image);
_lottie->markFrameShown();
}
} else if (_media) {
p.drawPixmap(_rect, paintedPixmap(_rect.size().toSize()), _rect);
}
}
class TopBarUser final : public TopBarAbstract {
public:
TopBarUser(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer);
void setPaused(bool paused) override;
void setTextPosition(int x, int y) override;
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
void updateTitle(
DocumentData *document,
TextWithEntities name,
not_null<Window::SessionController*> controller) const;
void updateAbout(DocumentData *document) const;
object_ptr<Ui::RpWidget> _content;
object_ptr<Ui::FlatLabel> _title;
object_ptr<Ui::FlatLabel> _about;
Ui::Premium::ColoredMiniStars _ministars;
struct {
object_ptr<Ui::RpWidget> widget;
Ui::Text::String text;
Ui::Animations::Simple animation;
bool shown = false;
QPoint position;
} _smallTop;
std::unique_ptr<EmojiStatusTopBar> _emojiStatus;
QImage _imageStar;
QRectF _ministarsRect;
QRectF _starRect;
};
TopBarUser::TopBarUser(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer)
: TopBarAbstract(parent)
, _content(this)
, _title(_content, st::settingsPremiumUserTitle)
, _about(_content, st::settingsPremiumUserAbout)
, _ministars(_content)
, _smallTop({
.widget = object_ptr<Ui::RpWidget>(this),
.text = Ui::Text::String(
st::boxTitle.style,
tr::lng_premium_summary_title(tr::now)),
}) {
_starRect = TopBarAbstract::starRect(1., 1.);
auto documentValue = Info::Profile::EmojiStatusIdValue(
peer
) | rpl::map([=](DocumentId id) -> DocumentData* {
const auto document = id
? controller->session().data().document(id).get()
: nullptr;
return (document && document->sticker()) ? document : nullptr;
});
rpl::combine(
std::move(documentValue),
Info::Profile::NameValue(peer)
) | rpl::start_with_next([=](
DocumentData *document,
TextWithEntities name) {
if (document) {
_emojiStatus = std::make_unique<EmojiStatusTopBar>(
document,
[=](QRect r) { update(std::move(r)); },
HistoryView::Sticker::EmojiSize());
_imageStar = QImage();
} else {
auto svg = QSvgRenderer(Svg());
const auto size = _starRect.size().toSize();
auto frame = QImage(
size * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
frame.setDevicePixelRatio(style::DevicePixelRatio());
auto mask = frame;
mask.fill(Qt::transparent);
{
Painter p(&mask);
auto gradient = QLinearGradient(
0,
size.height(),
size.width(),
0);
gradient.setStops(Ui::Premium::ButtonGradientStops());
p.setPen(Qt::NoPen);
p.setBrush(gradient);
p.drawRect(0, 0, size.width(), size.height());
}
frame.fill(Qt::transparent);
{
Painter q(&frame);
svg.render(&q, QRect(QPoint(), size));
q.setCompositionMode(QPainter::CompositionMode_SourceIn);
q.drawImage(0, 0, mask);
}
_imageStar = std::move(frame);
_emojiStatus = nullptr;
}
updateTitle(document, name, controller);
updateAbout(document);
}, lifetime());
rpl::combine(
_title->sizeValue(),
_about->sizeValue(),
_content->sizeValue()
) | rpl::start_with_next([=](
const QSize &titleSize,
const QSize &aboutSize,
const QSize &size) {
const auto rect = TopBarAbstract::starRect(1., 1.);
const auto &padding = st::settingsPremiumUserTitlePadding;
_title->moveToLeft(
(size.width() - titleSize.width()) / 2,
rect.top() + rect.height() + padding.top());
_about->moveToLeft(
(size.width() - aboutSize.width()) / 2,
_title->y() + titleSize.height() + padding.bottom());
const auto aboutBottom = _about->y() + _about->height();
const auto height = (aboutBottom > st::settingsPremiumUserHeight)
? aboutBottom + padding.bottom()
: st::settingsPremiumUserHeight;
{
const auto was = maximumHeight();
const auto now = height;
if (was != now) {
setMaximumHeight(now);
if (was == size.height()) {
resize(size.width(), now);
}
}
}
_content->resize(size.width(), maximumHeight());
}, lifetime());
sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
_content->resize(size.width(), maximumHeight());
_content->moveToLeft(0, -(_content->height() - size.height()));
_smallTop.widget->resize(size.width(), minimumHeight());
const auto shown = (minimumHeight() * 2 > size.height());
if (_smallTop.shown != shown) {
_smallTop.shown = shown;
_smallTop.animation.start(
[=] { _smallTop.widget->update(); },
_smallTop.shown ? 0. : 1.,
_smallTop.shown ? 1. : 0.,
st::infoTopBarDuration);
}
}, lifetime());
_smallTop.widget->paintRequest(
) | rpl::start_with_next([=] {
Painter p(_smallTop.widget);
p.setOpacity(_smallTop.animation.value(_smallTop.shown ? 1. : 0.));
paintEdges(p, st::boxBg);
p.setPen(st::boxTitleFg);
_smallTop.text.drawLeft(
p,
_smallTop.position.x(),
_smallTop.position.y(),
width(),
width());
}, lifetime());
_content->paintRequest(
) | rpl::start_with_next([=] {
Painter p(_content);
_ministars.paint(p);
if (_emojiStatus) {
_emojiStatus->paint(p);
} else if (!_imageStar.isNull()) {
p.drawImage(_starRect.topLeft(), _imageStar);
}
}, lifetime());
}
void TopBarUser::updateTitle(
DocumentData *document,
TextWithEntities name,
not_null<Window::SessionController*> controller) const {
if (!document) {
return _title->setMarkedText(
tr::lng_premium_summary_user_title(
tr::now,
lt_user,
std::move(name),
Ui::Text::WithEntities));
}
const auto stickerInfo = document->sticker();
if (!stickerInfo) {
return;
}
const auto &sets = document->owner().stickers().sets();
const auto it = sets.find(stickerInfo->set.id);
if (it == sets.cend()) {
return;
}
const auto set = it->second.get();
const auto text = (set->thumbnailDocumentId ? QChar('0') : QChar())
+ set->title;
const auto linkIndex = 1;
const auto entityEmojiData = Data::SerializeCustomEmojiId(
{ set->thumbnailDocumentId });
const auto entities = EntitiesInText{
{ EntityType::CustomEmoji, 0, 1, entityEmojiData },
Ui::Text::Link(text, linkIndex).entities.front(),
};
auto title = tr::lng_premium_emoji_status_title(
tr::now,
lt_user,
std::move(name),
lt_link,
{ .text = text, .entities = entities, },
Ui::Text::WithEntities);
const auto context = Core::MarkedTextContext{
.session = &controller->session(),
.customEmojiRepaint = [=] { _title->update(); },
};
_title->setMarkedText(std::move(title), context);
auto link = std::make_shared<LambdaClickHandler>([=,
stickerSetIdentifier = stickerInfo->set] {
controller->show(
Box<StickerSetBox>(
controller,
stickerSetIdentifier,
Data::StickersType::Emoji),
Ui::LayerOption::KeepOther);
});
_title->setLink(linkIndex, std::move(link));
}
void TopBarUser::updateAbout(DocumentData *document) const {
_about->setMarkedText((document
? tr::lng_premium_emoji_status_about
: tr::lng_premium_summary_user_about)(
tr::now,
Ui::Text::RichLangValue));
}
void TopBarUser::setPaused(bool paused) {
_ministars.setPaused(paused);
}
void TopBarUser::setTextPosition(int x, int y) {
_smallTop.position = { x, y };
}
void TopBarUser::paintEvent(QPaintEvent *e) {
Painter p(this);
TopBarAbstract::paintEdges(p, st::boxBg);
}
void TopBarUser::resizeEvent(QResizeEvent *e) {
_starRect = TopBarAbstract::starRect(1., 1.);
const auto &rect = _starRect;
const auto center = rect.center();
const auto size = QSize(
rect.width() * Ui::Premium::MiniStars::kSizeFactor,
rect.height());
const auto ministarsRect = QRect(
QPoint(center.x() - size.width(), center.y() - size.height()),
QPoint(center.x() + size.width(), center.y() + size.height()));
_ministars.setPosition(ministarsRect.topLeft());
_ministars.setSize(ministarsRect.size());
if (_emojiStatus) {
_emojiStatus->setCenter(_starRect.center());
}
}
class TopBar final : public TopBarAbstract {
public:
TopBar(
@ -399,7 +803,7 @@ TopBar::TopBar(
, _titlePadding(st::settingsPremiumTitlePadding)
, _about(this, std::move(about), st::settingsPremiumAbout)
, _ministars([=](const QRect &r) { update(r); })
, _star(u":/gui/icons/settings/star.svg"_q) {
, _star(Svg()) {
std::move(
title
) | rpl::start_with_next([=](QString text) {
@ -841,11 +1245,32 @@ QPointer<Ui::RpWidget> Premium::createPinnedToTop(
tr::lng_premium_summary_top_about(Ui::Text::RichLangValue));
}();
const auto content = Ui::CreateChild<TopBar>(
parent.get(),
_controller,
std::move(title),
std::move(about));
const auto emojiStatusData = Ref::EmojiStatus::Parse(_ref);
const auto isEmojiStatus = (!!emojiStatusData);
auto peerWithPremium = [&]() -> PeerData* {
if (isEmojiStatus) {
auto &data = _controller->session().data();
if (const auto peer = data.peer(emojiStatusData.peerId)) {
return peer;
}
}
return nullptr;
}();
const auto content = [&]() -> TopBarAbstract* {
if (peerWithPremium) {
return Ui::CreateChild<TopBarUser>(
parent.get(),
_controller,
peerWithPremium);
}
return Ui::CreateChild<TopBar>(
parent.get(),
_controller,
std::move(title),
std::move(about));
}();
_setPaused = [=](bool paused) {
content->setPaused(paused);
if (_subscribe) {
@ -858,7 +1283,9 @@ QPointer<Ui::RpWidget> Premium::createPinnedToTop(
content->setRoundEdges(wrap == Info::Wrap::Layer);
}, content->lifetime());
content->setMaximumHeight(st::introQrStepsTop);
content->setMaximumHeight(isEmojiStatus
? st::settingsPremiumUserHeight
: st::introQrStepsTop);
content->setMinimumHeight(st::infoLayerTopBarHeight);
content->resize(content->width(), content->maximumHeight());
@ -870,9 +1297,11 @@ QPointer<Ui::RpWidget> Premium::createPinnedToTop(
content,
object_ptr<Ui::IconButton>(
content,
isLayer
? st::settingsPremiumLayerTopBarBack
: st::settingsPremiumTopBarBack),
isEmojiStatus
? (isLayer ? st::infoTopBarBack : st::infoLayerTopBarBack)
: (isLayer
? st::settingsPremiumLayerTopBarBack
: st::settingsPremiumTopBarBack)),
st::infoTopBarScale);
_back->setDuration(0);
_back->toggleOn(isLayer
@ -894,7 +1323,9 @@ QPointer<Ui::RpWidget> Premium::createPinnedToTop(
} else {
_close = base::make_unique_q<Ui::IconButton>(
content,
st::settingsPremiumTopBarClose);
isEmojiStatus
? st::infoTopBarClose
: st::settingsPremiumTopBarClose);
_close->addClickHandler([=] {
_controller->parentController()->hideLayer();
_controller->parentController()->hideSpecialLayer();
@ -921,10 +1352,27 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
return nullptr;
}
auto buttonText = [&]() -> std::optional<rpl::producer<QString>> {
if (const auto emojiData = Ref::EmojiStatus::Parse(_ref)) {
auto &data = _controller->session().data();
if (const auto peer = data.peer(emojiData.peerId)) {
return Info::Profile::EmojiStatusIdValue(
peer
) | rpl::map([](DocumentId id) {
return id
? tr::lng_premium_emoji_status_button()
: tr::lng_premium_summary_user_button();
}) | rpl::flatten_latest();
}
}
return std::nullopt;
}();
_subscribe = CreateSubscribeButton({
_controller,
content,
[=] { return _ref; }
[ref = _ref] { return ref; },
std::move(buttonText),
});
_showFinished.events(