Implement effects paywalls.

This commit is contained in:
John Preston 2024-05-14 12:16:39 +04:00
parent d102d256a9
commit 732b67ca04
13 changed files with 241 additions and 47 deletions

View file

@ -564,6 +564,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_effect_add_title" = "Add an animated effect";
"lng_effect_stickers_title" = "Effects from stickers";
"lng_effect_send" = "Send with Effect";
"lng_effect_none" = "No effects found.";
"lng_effect_premium" = "Subscribe to {link} to add this animated effect.";
"lng_effect_premium_link" = "Telegram Premium";
"lng_languages" = "Languages";
"lng_languages_none" = "No languages found.";

View file

@ -73,7 +73,9 @@ using Data::StickersSet;
using Data::StickersPack;
using SetFlag = Data::StickersSetFlag;
[[nodiscard]] std::optional<QColor> ComputeImageColor(const QImage &frame) {
[[nodiscard]] std::optional<QColor> ComputeImageColor(
const style::icon &lockIcon,
const QImage &frame) {
if (frame.isNull()
|| frame.format() != QImage::Format_ARGB32_Premultiplied) {
return {};
@ -83,7 +85,7 @@ using SetFlag = Data::StickersSetFlag;
auto sb = int64();
auto sa = int64();
const auto factor = frame.devicePixelRatio();
const auto size = st::stickersPremiumLock.size() * factor;
const auto size = lockIcon.size() * factor;
const auto width = std::min(frame.width(), size.width());
const auto height = std::min(frame.height(), size.height());
const auto skipx = (frame.width() - width) / 2;
@ -110,22 +112,30 @@ using SetFlag = Data::StickersSetFlag;
}
[[nodiscard]] QColor ComputeLockColor(const QImage &frame) {
return ComputeImageColor(frame).value_or(st::windowSubTextFg->c);
[[nodiscard]] QColor ComputeLockColor(
const style::icon &lockIcon,
const QImage &frame) {
return ComputeImageColor(
lockIcon,
frame
).value_or(st::windowSubTextFg->c);
}
void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
void ValidatePremiumLockBg(
const style::icon &lockIcon,
QImage &image,
const QImage &frame) {
if (!image.isNull()) {
return;
}
const auto factor = style::DevicePixelRatio();
const auto size = st::stickersPremiumLock.size();
const auto size = lockIcon.size();
image = QImage(
size * factor,
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(factor);
auto p = QPainter(&image);
const auto color = ComputeLockColor(frame);
const auto color = ComputeLockColor(lockIcon, frame);
p.fillRect(
QRect(QPoint(), size),
anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
@ -134,12 +144,12 @@ void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
image = Images::Circle(std::move(image));
}
void ValidatePremiumStarFg(QImage &image) {
void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) {
if (!image.isNull()) {
return;
}
const auto factor = style::DevicePixelRatio();
const auto size = st::stickersPremiumLock.size();
const auto size = lockIcon.size();
image = QImage(
size * factor,
QImage::Format_ARGB32_Premultiplied);
@ -176,7 +186,10 @@ void ValidatePremiumStarFg(QImage &image) {
} // namespace
StickerPremiumMark::StickerPremiumMark(not_null<Main::Session*> session) {
StickerPremiumMark::StickerPremiumMark(
not_null<Main::Session*> session,
const style::icon &lockIcon)
: _lockIcon(lockIcon) {
style::PaletteChanged(
) | rpl::start_with_next([=] {
_lockGray = QImage();
@ -202,16 +215,14 @@ void StickerPremiumMark::paint(
const auto factor = style::DevicePixelRatio();
const auto radius = st::roundRadiusSmall;
const auto point = position + QPoint(
(_premium
? (singleSize.width() - (bg.width() / factor) - radius)
: (singleSize.width() - (bg.width() / factor)) / 2),
(singleSize.width() - (bg.width() / factor) - radius),
singleSize.height() - (bg.height() / factor) - radius);
p.drawImage(point, bg);
if (_premium) {
validateStar();
p.drawImage(point, _star);
} else {
st::stickersPremiumLock.paint(p, point, outerWidth);
_lockIcon.paint(p, point, outerWidth);
}
}
@ -219,11 +230,11 @@ void StickerPremiumMark::validateLock(
const QImage &frame,
QImage &backCache) {
auto &image = frame.isNull() ? _lockGray : backCache;
ValidatePremiumLockBg(image, frame);
ValidatePremiumLockBg(_lockIcon, image, frame);
}
void StickerPremiumMark::validateStar() {
ValidatePremiumStarFg(_star);
ValidatePremiumStarFg(_lockIcon, _star);
}
class StickerSetBox::Inner final : public Ui::RpWidget {
@ -664,7 +675,7 @@ StickerSetBox::Inner::Inner(
st::windowBgRipple,
st::windowBgOver,
[=] { repaintItems(); }))
, _premiumMark(_session)
, _premiumMark(_session, st::stickersPremiumLock)
, _updateItemsTimer([=] { updateItems(); })
, _input(set)
, _padding((type == Data::StickersType::Emoji)

View file

@ -30,7 +30,9 @@ class Show;
class StickerPremiumMark final {
public:
explicit StickerPremiumMark(not_null<Main::Session*> session);
StickerPremiumMark(
not_null<Main::Session*> session,
const style::icon &lockIcon);
void paint(
QPainter &p,
@ -44,6 +46,7 @@ private:
void validateLock(const QImage &frame, QImage &backCache);
void validateStar();
const style::icon &_lockIcon;
QImage _lockGray;
QImage _star;
bool _premium = false;

View file

@ -754,6 +754,7 @@ inlineResultsMinWidth: 48px;
inlineDurationMargin: 3px;
stickersPremiumLock: icon{{ "emoji/premium_lock", premiumButtonFg }};
emojiPremiumLock: icon{{ "chat/mini_lock", premiumButtonFg }};
reactStripExtend: margins(21px, 49px, 39px, 0px);
reactStripHeight: 40px;

View file

@ -136,6 +136,7 @@ struct EmojiListWidget::CustomEmojiInstance {
struct EmojiListWidget::RecentOne {
Ui::Text::CustomEmoji *custom = nullptr;
RecentEmojiId id;
mutable QImage premiumLock;
};
EmojiColorPicker::EmojiColorPicker(
@ -478,8 +479,12 @@ EmojiListWidget::EmojiListWidget(
, _localSetsManager(
std::make_unique<LocalStickersManager>(&session()))
, _customRecentFactory(std::move(descriptor.customRecentFactory))
, _freeEffects(std::move(descriptor.freeEffects))
, _customTextColor(std::move(descriptor.customTextColor))
, _overBg(st::emojiPanRadius, st().overBg)
, _premiumMark(std::make_unique<StickerPremiumMark>(
&session(),
st::emojiPremiumLock))
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
, _picker(this, st())
, _showPickerTimer([=] { showPicker(); })
@ -591,6 +596,10 @@ rpl::producer<std::vector<QString>> EmojiListWidget::searchQueries() const {
return _searchQueries.events();
}
rpl::producer<int> EmojiListWidget::recentShownCount() const {
return _recentShownCount.value();
}
void EmojiListWidget::applyNextSearchQuery() {
if (_searchQuery == _nextSearchQuery) {
return;
@ -612,6 +621,9 @@ void EmojiListWidget::applyNextSearchQuery() {
_searchCustomIds.clear();
}
resizeToWidth(width());
_recentShownCount = searching
? _searchResults.size()
: _recent.size();
update();
if (modeChanged) {
visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom());
@ -1024,9 +1036,10 @@ int EmojiListWidget::countDesiredHeight(int newWidth) {
const auto minimalLastHeight = std::max(
minimalHeight - padding.bottom(),
0);
return qMax(
minimalHeight,
countResult(minimalLastHeight) + padding.bottom());
const auto result = countResult(minimalLastHeight);
return result
? qMax(minimalHeight, result + padding.bottom())
: 0;
}
int EmojiListWidget::defaultMinimalHeight() const {
@ -1291,6 +1304,8 @@ void EmojiListWidget::paint(
QRect clip) {
validateEmojiPaintContext(context);
_paintAsPremium = session().premium();
auto fromColumn = floorclamp(
clip.x() - _rowsLeft,
_singleSize.width(),
@ -1455,16 +1470,44 @@ void EmojiListWidget::drawRecent(
QPoint position,
const RecentOne &recent) {
_recentPainted = true;
const auto locked = (_mode == Mode::MessageEffects)
&& !_paintAsPremium
&& v::is<RecentEmojiDocument>(recent.id.data)
&& !_freeEffects.contains(
v::get<RecentEmojiDocument>(recent.id.data).id);
auto lockedPainted = false;
if (locked) {
if (_premiumMarkFrameCache.isNull()) {
const auto ratio = style::DevicePixelRatio();
_premiumMarkFrameCache = QImage(
QSize(_customSingleSize, _customSingleSize) * ratio,
QImage::Format_ARGB32_Premultiplied);
_premiumMarkFrameCache.setDevicePixelRatio(ratio);
}
_premiumMarkFrameCache.fill(Qt::transparent);
}
if (const auto custom = recent.custom) {
_emojiPaintContext->scale = context.progress;
_emojiPaintContext->position = position
const auto exactPosition = position
+ _innerPosition
+ _customPosition;
_emojiPaintContext->scale = context.progress;
if (_mode == Mode::ChannelStatus) {
_emojiPaintContext->internal.forceFirstFrame
= (recent.id == _recent.front().id);
}
custom->paint(p, *_emojiPaintContext);
if (locked) {
lockedPainted = custom->ready();
auto q = Painter(&_premiumMarkFrameCache);
_emojiPaintContext->position = QPoint();
custom->paint(q, *_emojiPaintContext);
q.end();
p.drawImage(exactPosition, _premiumMarkFrameCache);
} else {
_emojiPaintContext->position = exactPosition;
custom->paint(p, *_emojiPaintContext);
}
} else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) {
if (_mode == Mode::EmojiStatus) {
position += QPoint(
@ -1478,6 +1521,16 @@ void EmojiListWidget::drawRecent(
} else {
Unexpected("Empty custom emoji in EmojiListWidget::drawRecent.");
}
if (locked) {
_premiumMark->paint(
p,
lockedPainted ? _premiumMarkFrameCache : QImage(),
recent.premiumLock,
position,
_singleSize,
width());
}
}
void EmojiListWidget::drawEmoji(

View file

@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/round_rect.h"
#include "base/timer.h"
class StickerPremiumMark;
namespace style {
struct EmojiPan;
} // namespace style
@ -89,6 +91,7 @@ struct EmojiListDescriptor {
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
DocumentId,
Fn<void()>)> customRecentFactory;
base::flat_set<DocumentId> freeEffects;
const style::EmojiPan *st = nullptr;
ComposeFeatures features;
};
@ -148,6 +151,7 @@ public:
const SendMenu::Details &details) override;
[[nodiscard]] rpl::producer<std::vector<QString>> searchQueries() const;
[[nodiscard]] rpl::producer<int> recentShownCount() const;
protected:
void visibleTopBottomUpdated(
@ -400,10 +404,13 @@ private:
int _counts[kEmojiSectionCount];
std::vector<RecentOne> _recent;
base::flat_set<DocumentId> _recentCustomIds;
base::flat_set<DocumentId> _freeEffects;
base::flat_set<uint64> _repaintsScheduled;
rpl::variable<int> _recentShownCount;
std::unique_ptr<Ui::Text::CustomEmojiPaintContext> _emojiPaintContext;
bool _recentPainted = false;
bool _grabbingChosen = false;
bool _paintAsPremium = false;
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
std::vector<CustomSet> _custom;
base::flat_set<DocumentId> _restrictedCustomList;
@ -417,6 +424,8 @@ private:
Ui::RoundRect _overBg;
QImage _searchExpandCache;
std::unique_ptr<StickerPremiumMark> _premiumMark;
QImage _premiumMarkFrameCache;
mutable std::unique_ptr<Ui::RippleAnimation> _colorAllRipple;
bool _colorAllRippleForced = false;
rpl::lifetime _colorAllRippleForcedLifetime;

View file

@ -891,7 +891,7 @@ FieldAutocomplete::Inner::Inner(
_st.pathBg,
_st.pathFg,
[=] { update(); }))
, _premiumMark(_session)
, _premiumMark(_session, st::stickersPremiumLock)
, _previewTimer([=] { showPreview(); }) {
_session->downloaderTaskFinished(
) | rpl::start_with_next([=] {

View file

@ -219,7 +219,9 @@ StickersListWidget::StickersListWidget(
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText))
, _settings(this, tr::lng_stickers_you_have(tr::now))
, _previewTimer([=] { showPreview(); })
, _premiumMark(std::make_unique<StickerPremiumMark>(&session()))
, _premiumMark(std::make_unique<StickerPremiumMark>(
&session(),
st::stickersPremiumLock))
, _searchRequestTimer([=] { sendSearchRequest(); }) {
setMouseTracking(true);
if (st().bg->c.alpha() > 0) {
@ -542,8 +544,8 @@ int StickersListWidget::countDesiredHeight(int newWidth) {
const auto minimalLastHeight = (_section == Section::Stickers)
? minimalHeight
: 0;
return qMax(minimalHeight, countResult(minimalLastHeight))
+ st::stickerPanPadding;
const auto result = qMax(minimalHeight, countResult(minimalLastHeight));
return result ? (result + st::stickerPanPadding) : 0;
}
void StickersListWidget::sendSearchRequest() {
@ -675,9 +677,14 @@ void StickersListWidget::refreshSearchRows(
_lastMousePosition = QCursor::pos();
resizeToWidth(width());
_recentShownCount = _filteredStickers.size();
updateSelected();
}
rpl::producer<int> StickersListWidget::recentShownCount() const {
return _recentShownCount.value();
}
void StickersListWidget::fillLocalSearchRows(const QString &query) {
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
if (searchWordsList.isEmpty()) {
@ -910,6 +917,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
toColumn = _columnCount - toColumn;
}
_paintAsPremium = session().premium();
_pathGradient->startFrame(0, width(), width() / 2);
auto &sets = shownSets();
@ -1489,7 +1497,7 @@ void StickersListWidget::paintSticker(
: (set.id == SearchEmojiSectionSetId())
? &_filterStickersCornerEmoji
: nullptr;
if (corner && !corner->empty()) {
if (corner && !corner->empty() && _paintAsPremium) {
Assert(index < corner->size());
if (const auto emoji = (*corner)[index]) {
const auto size = Ui::Emoji::GetSizeNormal();
@ -1960,6 +1968,11 @@ void StickersListWidget::setSection(Section section) {
}
clearHeavyData();
_section = section;
_recentShownCount = (section == Section::Search)
? _filteredStickers.size()
: _mySets.empty()
? 0
: _mySets.front().stickers.size();
}
void StickersListWidget::clearHeavyData() {
@ -2242,6 +2255,9 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
clearSelection();
auto recentPack = collectRecentStickers();
if (_section == Section::Stickers) {
_recentShownCount = recentPack.size();
}
auto recentIt = std::find_if(_mySets.begin(), _mySets.end(), [](auto &set) {
return set.id == Data::Stickers::RecentSetId;
});

View file

@ -128,6 +128,7 @@ public:
bool mySetsEmpty() const;
void applySearchQuery(std::vector<QString> &&query);
[[nodiscard]] rpl::producer<int> recentShownCount() const;
~StickersListWidget();
@ -388,6 +389,7 @@ private:
base::flat_set<not_null<DocumentData*>> _favedStickersMap;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
bool _paintAsPremium = false;
bool _showingSetById = false;
crl::time _lastScrolledAt = 0;
crl::time _lastFullUpdatedAt = 0;
@ -437,6 +439,7 @@ private:
std::vector<not_null<DocumentData*>> _filteredStickers;
std::vector<EmojiPtr> _filterStickersCornerEmoji;
rpl::variable<int> _recentShownCount;
std::map<QString, std::vector<uint64>> _searchCache;
std::vector<std::pair<uint64, QStringList>> _searchIndex;
base::Timer _searchRequestTimer;

View file

@ -972,6 +972,7 @@ void Selector::createList() {
: st::reactPanelScrollRounded);
_scroll->hide();
const auto effects = !_reactions.stickers.empty();
const auto st = lifetime().make_state<style::EmojiPan>(_st);
st->padding.setTop(_skipy);
if (!_reactions.customAllowed) {
@ -979,15 +980,35 @@ void Selector::createList() {
}
auto lists = _scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(_scroll));
auto recentList = _strip
? _unifiedFactoryOwner->unifiedIdsList()
: _recent;
auto freeEffects = base::flat_set<DocumentId>();
if (effects) {
auto free = base::flat_set<Data::ReactionId>();
free.reserve(_reactions.recent.size());
for (const auto &reaction : _reactions.recent) {
if (!reaction.premium) {
free.emplace(reaction.id);
}
}
for (const auto &id : recentList) {
const auto reactionId = _strip
? _unifiedFactoryOwner->lookupReactionId(id)
: Data::ReactionId{ id };
if (free.contains(reactionId)) {
freeEffects.insert(id);
}
}
}
_list = lists->add(
object_ptr<EmojiListWidget>(lists, EmojiListDescriptor{
.show = _show,
.mode = _listMode,
.paused = [] { return false; },
.customRecentList = (_strip
? _unifiedFactoryOwner->unifiedIdsList()
: _recent),
.customRecentList = std::move(recentList),
.customRecentFactory = _unifiedFactoryOwner->factory(),
.freeEffects = std::move(freeEffects),
.st = st,
}));
if (!_reactions.stickers.empty()) {
@ -1092,6 +1113,27 @@ void Selector::createList() {
) | rpl::start_with_next([=](std::vector<QString> &&query) {
_stickers->applySearchQuery(std::move(query));
}, _stickers->lifetime());
rpl::combine(
_list->recentShownCount(),
_stickers->recentShownCount()
) | rpl::start_with_next([=](int emoji, int stickers) {
_showEmptySearch = !emoji && !stickers;
_scroll->update();
}, _scroll->lifetime());
_scroll->paintRequest() | rpl::filter([=] {
return _showEmptySearch;
}) | rpl::start_with_next([=] {
auto p = QPainter(_scroll);
p.setPen(st::windowSubTextFg);
p.setFont(st::normalFont);
p.drawText(
_scroll->rect(),
tr::lng_effect_none(tr::now),
style::al_center);
}, _scroll->lifetime());
} else {
_list->setMinimalHeight(geometry.width(), _scroll->height());
}

View file

@ -205,6 +205,7 @@ private:
Ui::PlainShadow *_shadow = nullptr;
rpl::variable<int> _shadowTop = 0;
rpl::variable<int> _shadowSkip = 0;
bool _showEmptySearch = false;
QImage _paintBuffer;
Ui::Animations::Simple _expanding;

View file

@ -29,9 +29,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/chat_theme.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/painter.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
@ -42,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "settings/settings_premium.h"
#include "window/themes/window_theme.h"
#include "window/section_widget.h"
#include "styles/style_chat.h"
@ -89,6 +93,8 @@ private:
void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
[[nodiscard]] bool canSend() const;
void setupGeometry(QPoint position);
void setupBackground();
void setupItem();
@ -110,6 +116,9 @@ private:
const AdminLog::OwnedItem _replyTo;
const AdminLog::OwnedItem _item;
const std::unique_ptr<Ui::FlatButton> _send;
const std::unique_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _premiumPromoLabel;
const not_null<Ui::RpWidget*> _bottom;
const Fn<void()> _close;
const Fn<void(Action, Details)> _actionWithEffect;
QImage _icon;
@ -236,11 +245,26 @@ EffectPreview::EffectPreview(
_replyTo->data()->fullId(),
tr::lng_settings_chat_message_reply(tr::now),
_effectId))
, _send(
std::make_unique<BottomRounded>(
, _send(canSend()
? std::make_unique<BottomRounded>(
this,
tr::lng_effect_send(tr::now),
st::effectPreviewSend))
st::effectPreviewSend)
: nullptr)
, _premiumPromoLabel(canSend()
? nullptr
: std::make_unique<Ui::PaddingWrap<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(
this,
tr::lng_effect_premium(
lt_link,
tr::lng_effect_premium_link() | Ui::Text::ToLink(),
Ui::Text::WithEntities),
st::effectPreviewPromoLabel),
st::effectPreviewPromoPadding))
, _bottom(_send ? ((Ui::RpWidget*)_send.get()) : _premiumPromoLabel.get())
, _close(done)
, _actionWithEffect(ComposeActionWithEffect(action, _effectId, done)) {
setupGeometry(position);
setupBackground();
@ -291,8 +315,9 @@ void EffectPreview::setupGeometry(QPoint position) {
const auto shadow = st::previewMenu.shadow;
const auto extend = shadow.extend;
_inner = QRect(QPoint(extend.left(), extend.top()), innerSize);
_bottom->resizeToWidth(_inner.width());
const auto size = _inner.marginsAdded(extend).size()
+ QSize(0, _send->height());
+ QSize(0, _bottom->height());
const auto left = std::max(
std::min(
position.x() - size.width() / 2,
@ -305,11 +330,11 @@ void EffectPreview::setupGeometry(QPoint position) {
parent->height() - size.height()),
topMin);
setGeometry(left, top, size.width(), size.height());
_send->setGeometry(
_bottom->setGeometry(
_inner.x(),
_inner.y() + _inner.height(),
_inner.width(),
_send->height());
_bottom->height());
}
void EffectPreview::setupBackground() {
@ -343,7 +368,7 @@ void EffectPreview::setupItem() {
void EffectPreview::repaintBackground() {
const auto ratio = style::DevicePixelRatio();
const auto inner = _inner.size() + QSize(0, _send->height());
const auto inner = _inner.size() + QSize(0, _bottom->height());
auto bg = QImage(
inner * ratio,
QImage::Format_ARGB32_Premultiplied);
@ -357,7 +382,7 @@ void EffectPreview::repaintBackground() {
QSize(inner.width(), inner.height() * 5),
QRect(QPoint(), inner));
p.fillRect(
QRect(0, _inner.height(), _inner.width(), _send->height()),
QRect(0, _inner.height(), _inner.width(), _bottom->height()),
st::previewMarkRead.bgColor);
auto hq = PainterHighQualityEnabler(p);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
@ -409,14 +434,32 @@ void EffectPreview::createLottie() {
}, raw->lifetime());
}
bool EffectPreview::canSend() const {
return !_effect.premium || _show->session().premium();
}
void EffectPreview::setupSend(Details details) {
_send->setClickedCallback([=] {
_actionWithEffect(Api::SendOptions(), details);
});
const auto type = details.type;
SetupMenuAndShortcuts(_send.get(), _show, [=] {
return Details{ .type = type };
}, _actionWithEffect);
if (_send) {
_send->setClickedCallback([=] {
_actionWithEffect(Api::SendOptions(), details);
});
const auto type = details.type;
SetupMenuAndShortcuts(_send.get(), _show, [=] {
return Details{ .type = type };
}, _actionWithEffect);
} else {
_premiumPromoLabel->entity()->setClickHandlerFilter([=](auto&&...) {
const auto window = _show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
if (window) {
if (const auto onstack = _close) {
onstack();
}
Settings::ShowPremium(window, "message_effect");
}
return false;
});
}
}
bool EffectPreview::checkReady() {

View file

@ -1120,3 +1120,12 @@ effectPreviewSend: FlatButton(previewMarkRead) {
bgColor: transparent;
overBgColor: transparent;
}
effectPreviewPromoLabel: FlatLabel(defaultFlatLabel) {
minWidth: 64px;
align: align(top);
textFg: windowSubTextFg;
style: TextStyle(defaultTextStyle) {
font: font(11px);
}
}
effectPreviewPromoPadding: margins(4px, 6px, 4px, 6px);