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_add_title" = "Add an animated effect";
"lng_effect_stickers_title" = "Effects from stickers"; "lng_effect_stickers_title" = "Effects from stickers";
"lng_effect_send" = "Send with Effect"; "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" = "Languages";
"lng_languages_none" = "No languages found."; "lng_languages_none" = "No languages found.";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -972,6 +972,7 @@ void Selector::createList() {
: st::reactPanelScrollRounded); : st::reactPanelScrollRounded);
_scroll->hide(); _scroll->hide();
const auto effects = !_reactions.stickers.empty();
const auto st = lifetime().make_state<style::EmojiPan>(_st); const auto st = lifetime().make_state<style::EmojiPan>(_st);
st->padding.setTop(_skipy); st->padding.setTop(_skipy);
if (!_reactions.customAllowed) { if (!_reactions.customAllowed) {
@ -979,15 +980,35 @@ void Selector::createList() {
} }
auto lists = _scroll->setOwnedWidget( auto lists = _scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(_scroll)); 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( _list = lists->add(
object_ptr<EmojiListWidget>(lists, EmojiListDescriptor{ object_ptr<EmojiListWidget>(lists, EmojiListDescriptor{
.show = _show, .show = _show,
.mode = _listMode, .mode = _listMode,
.paused = [] { return false; }, .paused = [] { return false; },
.customRecentList = (_strip .customRecentList = std::move(recentList),
? _unifiedFactoryOwner->unifiedIdsList()
: _recent),
.customRecentFactory = _unifiedFactoryOwner->factory(), .customRecentFactory = _unifiedFactoryOwner->factory(),
.freeEffects = std::move(freeEffects),
.st = st, .st = st,
})); }));
if (!_reactions.stickers.empty()) { if (!_reactions.stickers.empty()) {
@ -1092,6 +1113,27 @@ void Selector::createList() {
) | rpl::start_with_next([=](std::vector<QString> &&query) { ) | rpl::start_with_next([=](std::vector<QString> &&query) {
_stickers->applySearchQuery(std::move(query)); _stickers->applySearchQuery(std::move(query));
}, _stickers->lifetime()); }, _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 { } else {
_list->setMinimalHeight(geometry.width(), _scroll->height()); _list->setMinimalHeight(geometry.width(), _scroll->height());
} }

View file

@ -205,6 +205,7 @@ private:
Ui::PlainShadow *_shadow = nullptr; Ui::PlainShadow *_shadow = nullptr;
rpl::variable<int> _shadowTop = 0; rpl::variable<int> _shadowTop = 0;
rpl::variable<int> _shadowSkip = 0; rpl::variable<int> _shadowSkip = 0;
bool _showEmptySearch = false;
QImage _paintBuffer; QImage _paintBuffer;
Ui::Animations::Simple _expanding; 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/chat/chat_theme.h"
#include "ui/effects/path_shift_gradient.h" #include "ui/effects/path_shift_gradient.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.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 "data/data_session.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "settings/settings_premium.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/section_widget.h" #include "window/section_widget.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -89,6 +93,8 @@ private:
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override;
[[nodiscard]] bool canSend() const;
void setupGeometry(QPoint position); void setupGeometry(QPoint position);
void setupBackground(); void setupBackground();
void setupItem(); void setupItem();
@ -110,6 +116,9 @@ private:
const AdminLog::OwnedItem _replyTo; const AdminLog::OwnedItem _replyTo;
const AdminLog::OwnedItem _item; const AdminLog::OwnedItem _item;
const std::unique_ptr<Ui::FlatButton> _send; 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; const Fn<void(Action, Details)> _actionWithEffect;
QImage _icon; QImage _icon;
@ -236,11 +245,26 @@ EffectPreview::EffectPreview(
_replyTo->data()->fullId(), _replyTo->data()->fullId(),
tr::lng_settings_chat_message_reply(tr::now), tr::lng_settings_chat_message_reply(tr::now),
_effectId)) _effectId))
, _send( , _send(canSend()
std::make_unique<BottomRounded>( ? std::make_unique<BottomRounded>(
this, this,
tr::lng_effect_send(tr::now), 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)) { , _actionWithEffect(ComposeActionWithEffect(action, _effectId, done)) {
setupGeometry(position); setupGeometry(position);
setupBackground(); setupBackground();
@ -291,8 +315,9 @@ void EffectPreview::setupGeometry(QPoint position) {
const auto shadow = st::previewMenu.shadow; const auto shadow = st::previewMenu.shadow;
const auto extend = shadow.extend; const auto extend = shadow.extend;
_inner = QRect(QPoint(extend.left(), extend.top()), innerSize); _inner = QRect(QPoint(extend.left(), extend.top()), innerSize);
_bottom->resizeToWidth(_inner.width());
const auto size = _inner.marginsAdded(extend).size() const auto size = _inner.marginsAdded(extend).size()
+ QSize(0, _send->height()); + QSize(0, _bottom->height());
const auto left = std::max( const auto left = std::max(
std::min( std::min(
position.x() - size.width() / 2, position.x() - size.width() / 2,
@ -305,11 +330,11 @@ void EffectPreview::setupGeometry(QPoint position) {
parent->height() - size.height()), parent->height() - size.height()),
topMin); topMin);
setGeometry(left, top, size.width(), size.height()); setGeometry(left, top, size.width(), size.height());
_send->setGeometry( _bottom->setGeometry(
_inner.x(), _inner.x(),
_inner.y() + _inner.height(), _inner.y() + _inner.height(),
_inner.width(), _inner.width(),
_send->height()); _bottom->height());
} }
void EffectPreview::setupBackground() { void EffectPreview::setupBackground() {
@ -343,7 +368,7 @@ void EffectPreview::setupItem() {
void EffectPreview::repaintBackground() { void EffectPreview::repaintBackground() {
const auto ratio = style::DevicePixelRatio(); 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( auto bg = QImage(
inner * ratio, inner * ratio,
QImage::Format_ARGB32_Premultiplied); QImage::Format_ARGB32_Premultiplied);
@ -357,7 +382,7 @@ void EffectPreview::repaintBackground() {
QSize(inner.width(), inner.height() * 5), QSize(inner.width(), inner.height() * 5),
QRect(QPoint(), inner)); QRect(QPoint(), inner));
p.fillRect( p.fillRect(
QRect(0, _inner.height(), _inner.width(), _send->height()), QRect(0, _inner.height(), _inner.width(), _bottom->height()),
st::previewMarkRead.bgColor); st::previewMarkRead.bgColor);
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
@ -409,14 +434,32 @@ void EffectPreview::createLottie() {
}, raw->lifetime()); }, raw->lifetime());
} }
bool EffectPreview::canSend() const {
return !_effect.premium || _show->session().premium();
}
void EffectPreview::setupSend(Details details) { void EffectPreview::setupSend(Details details) {
_send->setClickedCallback([=] { if (_send) {
_actionWithEffect(Api::SendOptions(), details); _send->setClickedCallback([=] {
}); _actionWithEffect(Api::SendOptions(), details);
const auto type = details.type; });
SetupMenuAndShortcuts(_send.get(), _show, [=] { const auto type = details.type;
return Details{ .type = type }; SetupMenuAndShortcuts(_send.get(), _show, [=] {
}, _actionWithEffect); 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() { bool EffectPreview::checkReady() {

View file

@ -1120,3 +1120,12 @@ effectPreviewSend: FlatButton(previewMarkRead) {
bgColor: transparent; bgColor: transparent;
overBgColor: 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);