2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-08-31 14:38:15 +00:00

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

@@ -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(