Introduce premium reactions preview box.
This commit is contained in:
parent
1a2a65ff19
commit
2362d6c6fb
|
@ -260,6 +260,8 @@ PRIVATE
|
|||
boxes/pin_messages_box.h
|
||||
boxes/premium_limits_box.cpp
|
||||
boxes/premium_limits_box.h
|
||||
boxes/premium_preview_box.cpp
|
||||
boxes/premium_preview_box.h
|
||||
boxes/reactions_settings_box.cpp
|
||||
boxes/reactions_settings_box.h
|
||||
boxes/report_messages_box.cpp
|
||||
|
@ -274,8 +276,6 @@ PRIVATE
|
|||
boxes/sessions_box.h
|
||||
boxes/share_box.cpp
|
||||
boxes/share_box.h
|
||||
boxes/sticker_preview_box.cpp
|
||||
boxes/sticker_preview_box.h
|
||||
boxes/sticker_set_box.cpp
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
|
|
|
@ -0,0 +1,782 @@
|
|||
/*
|
||||
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 "boxes/premium_preview_box.h"
|
||||
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "chat_helpers/stickers_emoji_pack.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/text/text.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPremiumShift = 0.082;
|
||||
constexpr auto kShiftDuration = crl::time(200);
|
||||
constexpr auto kEnumerateCount = 3;
|
||||
|
||||
struct Descriptor {
|
||||
PremiumPreview section = PremiumPreview::Stickers;
|
||||
DocumentData *requestedSticker = nullptr;
|
||||
base::flat_set<QString> disabledReactions;
|
||||
};
|
||||
|
||||
bool operator==(const Descriptor &a, const Descriptor &b) {
|
||||
return (a.section == b.section)
|
||||
&& (a.requestedSticker == b.requestedSticker)
|
||||
&& (a.disabledReactions == b.disabledReactions);
|
||||
}
|
||||
|
||||
bool operator!=(const Descriptor &a, const Descriptor &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
struct Preload {
|
||||
Descriptor descriptor;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
base::weak_ptr<Window::SessionController> controller;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<Preload> &Preloads() {
|
||||
static auto result = std::vector<Preload>();
|
||||
return result;
|
||||
}
|
||||
|
||||
void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
const auto origin = media->owner()->stickerSetOrigin();
|
||||
media->automaticLoad(origin, nullptr);
|
||||
media->videoThumbnailWanted(origin);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> ChatBackPreview(
|
||||
QWidget *parent,
|
||||
int height,
|
||||
const QImage &back) {
|
||||
auto result = object_ptr<Ui::FixedHeightWidget>(parent, height);
|
||||
const auto raw = result.data();
|
||||
|
||||
raw->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
p.drawImage(0, 0, back);
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> StickerPreview(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
using namespace HistoryView;
|
||||
const auto document = media->owner();
|
||||
const auto lottieSize = Sticker::Size(document);
|
||||
const auto effectSize = Sticker::PremiumEffectSize(document);
|
||||
const auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
parent->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
result->setGeometry(QRect(
|
||||
QPoint(
|
||||
(size.width() - effectSize.width()) / 2,
|
||||
(size.height() - effectSize.height()) / 2),
|
||||
effectSize));
|
||||
}, result->lifetime());
|
||||
auto &lifetime = result->lifetime();
|
||||
|
||||
struct State {
|
||||
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
std::unique_ptr<Lottie::SinglePlayer> effect;
|
||||
std::unique_ptr<Ui::PathShiftGradient> pathGradient;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto createLottieIfReady = [=] {
|
||||
if (state->lottie) {
|
||||
return;
|
||||
}
|
||||
const auto document = media->owner();
|
||||
const auto sticker = document->sticker();
|
||||
if (!sticker || !sticker->isLottie() || !media->loaded()) {
|
||||
return;
|
||||
} else if (media->videoThumbnailContent().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
state->lottie = ChatHelpers::LottiePlayerFromDocument(
|
||||
media.get(),
|
||||
nullptr,
|
||||
ChatHelpers::StickerLottieSize::MessageHistory,
|
||||
lottieSize * factor,
|
||||
Lottie::Quality::High);
|
||||
state->effect = document->session().emojiStickersPack().effectPlayer(
|
||||
document,
|
||||
media->videoThumbnailContent(),
|
||||
QString(),
|
||||
true);
|
||||
|
||||
const auto update = [=] { result->update(); };
|
||||
auto &lifetime = result->lifetime();
|
||||
state->lottie->updates() | rpl::start_with_next(update, lifetime);
|
||||
state->effect->updates() | rpl::start_with_next(update, lifetime);
|
||||
};
|
||||
state->pathGradient = MakePathShiftGradient(
|
||||
controller->chatStyle(),
|
||||
[=] { result->update(); });
|
||||
|
||||
result->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
createLottieIfReady();
|
||||
|
||||
auto p = QPainter(result);
|
||||
|
||||
const auto left = effectSize.width()
|
||||
- int(lottieSize.width() * (1. + kPremiumShift));
|
||||
const auto top = (effectSize.height() - lottieSize.height()) / 2;
|
||||
const auto r = QRect(QPoint(left, top), lottieSize);
|
||||
if (!state->lottie
|
||||
|| !state->lottie->ready()
|
||||
|| !state->effect->ready()) {
|
||||
p.setBrush(controller->chatStyle()->msgServiceBg());
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
r,
|
||||
state->pathGradient.get());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto frame = state->lottie->frameInfo({ lottieSize * factor });
|
||||
const auto effect = state->effect->frameInfo(
|
||||
{ effectSize * factor });
|
||||
//const auto framesCount = !frame.image.isNull()
|
||||
// ? state->lottie->framesCount()
|
||||
// : 1;
|
||||
//const auto effectsCount = !effect.image.isNull()
|
||||
// ? state->effect->framesCount()
|
||||
// : 1;
|
||||
|
||||
p.drawImage(r, frame.image);
|
||||
p.drawImage(result->rect(), effect.image);
|
||||
|
||||
if (!frame.image.isNull()/*
|
||||
&& ((frame.index % effectsCount) <= effect.index)*/) {
|
||||
state->lottie->markFrameShown();
|
||||
}
|
||||
if (!effect.image.isNull()/*
|
||||
&& ((effect.index % framesCount) <= frame.index)*/) {
|
||||
state->effect->markFrameShown();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class ReactionPreview final {
|
||||
public:
|
||||
ReactionPreview(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::Reaction &reaction,
|
||||
Fn<void()> update);
|
||||
|
||||
[[nodiscard]] bool playsEffect() const;
|
||||
void paint(QPainter &p, int x, int y, float64 scale);
|
||||
void paintEffect(QPainter &p, int x, int y, float64 scale);
|
||||
|
||||
void startAnimations();
|
||||
void cancelAnimations();
|
||||
|
||||
private:
|
||||
void checkReady();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const Fn<void()> _update;
|
||||
std::shared_ptr<Data::DocumentMedia> _centerMedia;
|
||||
std::shared_ptr<Data::DocumentMedia> _aroundMedia;
|
||||
std::unique_ptr<Lottie::SinglePlayer> _center;
|
||||
std::unique_ptr<Lottie::SinglePlayer> _around;
|
||||
std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
QImage _cache1;
|
||||
QImage _cache2;
|
||||
QImage _cache3;
|
||||
bool _playRequested = false;
|
||||
bool _aroundPlaying = false;
|
||||
bool _centerPlaying = false;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
ReactionPreview::ReactionPreview(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::Reaction &reaction,
|
||||
Fn<void()> update)
|
||||
: _controller(controller)
|
||||
, _update(std::move(update))
|
||||
, _centerMedia(reaction.centerIcon->createMediaView())
|
||||
, _aroundMedia(reaction.aroundAnimation->createMediaView())
|
||||
, _pathGradient(
|
||||
HistoryView::MakePathShiftGradient(
|
||||
controller->chatStyle(),
|
||||
_update)) {
|
||||
_centerMedia->checkStickerLarge();
|
||||
_aroundMedia->checkStickerLarge();
|
||||
checkReady();
|
||||
}
|
||||
|
||||
void ReactionPreview::checkReady() {
|
||||
const auto make = [&](
|
||||
const std::shared_ptr<Data::DocumentMedia> &media,
|
||||
int size) {
|
||||
const auto bytes = media->bytes();
|
||||
const auto filepath = media->owner()->filepath();
|
||||
auto result = ChatHelpers::LottiePlayerFromDocument(
|
||||
media.get(),
|
||||
nullptr,
|
||||
ChatHelpers::StickerLottieSize::PremiumReactionPreview,
|
||||
QSize(size, size) * style::DevicePixelRatio(),
|
||||
Lottie::Quality::High);
|
||||
result->updates() | rpl::start_with_next(_update, _lifetime);
|
||||
return result;
|
||||
};
|
||||
if (!_center && _centerMedia->loaded()) {
|
||||
_center = make(_centerMedia, st::premiumReactionSize);
|
||||
}
|
||||
if (!_around && _aroundMedia->loaded()) {
|
||||
_around = make(_aroundMedia, st::premiumReactionAround);
|
||||
}
|
||||
}
|
||||
|
||||
void ReactionPreview::startAnimations() {
|
||||
_playRequested = true;
|
||||
if (!_center || !_center->ready() || !_around || !_around->ready()) {
|
||||
return;
|
||||
}
|
||||
_update();
|
||||
}
|
||||
|
||||
void ReactionPreview::cancelAnimations() {
|
||||
_playRequested = false;
|
||||
}
|
||||
|
||||
void ReactionPreview::paint(QPainter &p, int x, int y, float64 scale) {
|
||||
const auto size = st::premiumReactionAround;
|
||||
const auto center = st::premiumReactionSize;
|
||||
const auto inner = QRect(
|
||||
x + (size - center) / 2,
|
||||
y + (size - center) / 2,
|
||||
center,
|
||||
center);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto centerReady = _center && _center->ready();
|
||||
const auto staticCenter = centerReady && !_centerPlaying;
|
||||
const auto use1 = staticCenter && scale == st::premiumReactionScale1;
|
||||
const auto use2 = staticCenter && scale == st::premiumReactionScale2;
|
||||
const auto use3 = staticCenter && scale == st::premiumReactionScale3;
|
||||
const auto useScale = (!use1 && !use2 && !use3 && scale != 1.);
|
||||
if (useScale) {
|
||||
p.save();
|
||||
p.translate(inner.center());
|
||||
p.scale(scale, scale);
|
||||
p.translate(-inner.center());
|
||||
}
|
||||
checkReady();
|
||||
if (centerReady) {
|
||||
if (use1 || use2 || use3) {
|
||||
auto &cache = use1 ? _cache1 : use2 ? _cache2 : _cache3;
|
||||
const auto use = int(std::round(center * scale));
|
||||
const auto rect = QRect(
|
||||
x + (size - use) / 2,
|
||||
y + (size - use) / 2,
|
||||
use,
|
||||
use);
|
||||
if (cache.isNull()) {
|
||||
cache = _center->frame().scaledToWidth(
|
||||
use * style::DevicePixelRatio(),
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
p.drawImage(rect, cache);
|
||||
} else {
|
||||
p.drawImage(inner, _center->frame());
|
||||
}
|
||||
if (_aroundPlaying) {
|
||||
const auto almost = (_around->frameIndex() + 1)
|
||||
== _around->framesCount();
|
||||
const auto marked = _around->markFrameShown();
|
||||
if (almost && marked) {
|
||||
_aroundPlaying = false;
|
||||
}
|
||||
}
|
||||
if (_centerPlaying) {
|
||||
const auto almost = (_center->frameIndex() + 1)
|
||||
== _center->framesCount();
|
||||
const auto marked = _center->markFrameShown();
|
||||
if (almost && marked) {
|
||||
_centerPlaying = false;
|
||||
}
|
||||
}
|
||||
if (_around
|
||||
&& _around->ready()
|
||||
&& !_aroundPlaying
|
||||
&& !_centerPlaying
|
||||
&& _playRequested) {
|
||||
_aroundPlaying = _centerPlaying = true;
|
||||
_playRequested = false;
|
||||
}
|
||||
} else {
|
||||
p.setBrush(_controller->chatStyle()->msgServiceBg());
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
p,
|
||||
_centerMedia.get(),
|
||||
inner,
|
||||
_pathGradient.get());
|
||||
}
|
||||
if (useScale) {
|
||||
p.restore();
|
||||
}
|
||||
}
|
||||
|
||||
bool ReactionPreview::playsEffect() const {
|
||||
return _aroundPlaying;
|
||||
}
|
||||
|
||||
void ReactionPreview::paintEffect(QPainter &p, int x, int y, float64 scale) {
|
||||
if (!_aroundPlaying) {
|
||||
return;
|
||||
}
|
||||
const auto size = st::premiumReactionAround;
|
||||
const auto outer = QRect(x, y, size, size);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
if (scale != 1.) {
|
||||
p.save();
|
||||
p.translate(outer.center());
|
||||
p.scale(scale, scale);
|
||||
p.translate(-outer.center());
|
||||
}
|
||||
p.drawImage(outer, _around->frame());
|
||||
if (scale != 1.) {
|
||||
p.restore();
|
||||
}
|
||||
if (_aroundPlaying
|
||||
&& (_around->frameIndex() + 1 == _around->framesCount())
|
||||
&& _around->markFrameShown()) {
|
||||
_aroundPlaying = false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> ReactionsPreview(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller) {
|
||||
struct State {
|
||||
std::vector<std::unique_ptr<ReactionPreview>> entries;
|
||||
Ui::Animations::Simple shifting;
|
||||
int shift = 2;
|
||||
bool played = false;
|
||||
bool inside = false;
|
||||
};
|
||||
const auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
auto &lifetime = result->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
result->setMouseTracking(true);
|
||||
|
||||
parent->sizeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
result->setGeometry(parent->rect());
|
||||
}, result->lifetime());
|
||||
|
||||
using namespace HistoryView;
|
||||
const auto list = controller->session().data().reactions().list(
|
||||
Data::Reactions::Type::Active);
|
||||
for (const auto &reaction : list) {
|
||||
if (!reaction.premium
|
||||
|| !reaction.centerIcon
|
||||
|| !reaction.aroundAnimation) {
|
||||
continue;
|
||||
}
|
||||
state->entries.push_back(std::make_unique<ReactionPreview>(
|
||||
controller,
|
||||
reaction,
|
||||
[=] { result->update(); }));
|
||||
}
|
||||
const auto enumerate = [=](
|
||||
Fn<void(not_null<ReactionPreview*>,int,float64,int)> callback) {
|
||||
const auto count = int(state->entries.size());
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
const auto computeLeft = [](int index) {
|
||||
const auto skips = std::array{
|
||||
st::premiumReactionSkip1,
|
||||
st::premiumReactionSkip2,
|
||||
st::premiumReactionSkip3,
|
||||
};
|
||||
const auto id = std::abs(index);
|
||||
const auto delta = !id
|
||||
? 0
|
||||
: (id <= skips.size())
|
||||
? std::accumulate(begin(skips), begin(skips) + id, 0)
|
||||
: (ranges::accumulate(skips, 0)
|
||||
+ skips.back() * int(id - skips.size()));
|
||||
return (st::boxWideWidth / 2)
|
||||
+ (index < 0 ? -delta : delta)
|
||||
- st::premiumReactionAround / 2;
|
||||
};
|
||||
const auto computeScale = [](int index) {
|
||||
const auto id = std::abs(index);
|
||||
const auto scales = std::array{
|
||||
st::premiumReactionScale1,
|
||||
st::premiumReactionScale2,
|
||||
st::premiumReactionScale3,
|
||||
};
|
||||
return !id
|
||||
? 1.
|
||||
: scales[std::min(id, int(scales.size())) - 1];
|
||||
};
|
||||
const auto shift = state->shifting.value(state->shift);
|
||||
const auto delta = !state->shifting.animating()
|
||||
? state->shift
|
||||
: (shift < 0)
|
||||
? -(int(std::floor(-shift)) + 1)
|
||||
: int(std::floor(shift));
|
||||
const auto progress = shift - delta;
|
||||
const auto start = delta - kEnumerateCount;
|
||||
const auto from = ((start % count) + count) % count;
|
||||
const auto till = from + kEnumerateCount * 2 + 1;
|
||||
const auto outerSize = st::premiumReactionAround;
|
||||
for (auto i = from; i != till; ++i) {
|
||||
const auto index = (i - from) - kEnumerateCount;
|
||||
auto left = computeLeft(index);
|
||||
auto scale = computeScale(index);
|
||||
if (progress > 0.) {
|
||||
left = anim::interpolate(
|
||||
left,
|
||||
computeLeft(index - 1),
|
||||
progress);
|
||||
scale = scale + (computeScale(index - 1) - scale) * progress;
|
||||
}
|
||||
const auto entry = state->entries[i % count].get();
|
||||
const auto scaledSize = scale * st::premiumReactionSize;
|
||||
const auto paintedLeft = left + (outerSize - scaledSize) / 2;
|
||||
const auto paintedRight = left + (outerSize + scaledSize) / 2;
|
||||
if (entry->playsEffect()
|
||||
|| (paintedRight > 0 && paintedLeft < st::boxWideWidth)) {
|
||||
callback(entry, left, scale, delta + index);
|
||||
} else {
|
||||
int a = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
result->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(result);
|
||||
const auto top = result->height() / 2 - st::premiumReactionTop;
|
||||
auto effects = std::vector<Fn<void()>>();
|
||||
if (!state->played && !state->shifting.animating()) {
|
||||
state->played = true;
|
||||
if (const auto count = state->entries.size()) {
|
||||
const auto index = ((state->shift % count) + count) % count;
|
||||
state->entries[index]->startAnimations();
|
||||
}
|
||||
}
|
||||
enumerate([&](
|
||||
not_null<ReactionPreview*> entry,
|
||||
int left,
|
||||
float64 scale,
|
||||
int index) {
|
||||
entry->paint(p, left, top, scale);
|
||||
if (entry->playsEffect()) {
|
||||
effects.push_back([=, &p] {
|
||||
entry->paintEffect(p, left, top, scale);
|
||||
});
|
||||
}
|
||||
});
|
||||
for (const auto &paint : effects) {
|
||||
paint();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
const auto lookup = [=](QPoint point) -> std::optional<int> {
|
||||
auto found = std::optional<int>();
|
||||
const auto top = result->height() / 2 - st::premiumReactionTop;
|
||||
enumerate([&](auto, int left, float64 scale, int index) {
|
||||
const auto size = int(st::premiumReactionSize * scale) / 2;
|
||||
const auto outer = st::premiumReactionAround / 2;
|
||||
const auto rect = QRect(
|
||||
left + outer - (size / 2),
|
||||
top + outer - (size / 2),
|
||||
size,
|
||||
size);
|
||||
if (rect.contains(point)) {
|
||||
found = index;
|
||||
}
|
||||
});
|
||||
return found;
|
||||
};
|
||||
result->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
const auto point = static_cast<QMouseEvent*>(event.get())->pos();
|
||||
if (const auto index = lookup(point)) {
|
||||
state->shifting.start(
|
||||
[=] { result->update(); },
|
||||
state->shift,
|
||||
*index,
|
||||
kShiftDuration,
|
||||
anim::sineInOut);
|
||||
state->shift = *index;
|
||||
state->played = false;
|
||||
}
|
||||
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
const auto point = static_cast<QMouseEvent*>(event.get())->pos();
|
||||
const auto inside = lookup(point).has_value();
|
||||
if (state->inside != inside) {
|
||||
state->inside = inside;
|
||||
result->setCursor(inside
|
||||
? style::cur_pointer
|
||||
: style::cur_default);
|
||||
}
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::AbstractButton> CreateGradientButton(
|
||||
QWidget *parent,
|
||||
QGradientStops stops) {
|
||||
return object_ptr<Ui::GradientButton>(parent, std::move(stops));
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::AbstractButton> CreatePremiumButton(
|
||||
QWidget *parent) {
|
||||
return CreateGradientButton(parent, Ui::Premium::ButtonGradientStops());
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::AbstractButton> CreateUnlockButton(
|
||||
QWidget *parent,
|
||||
int width) {
|
||||
auto result = CreatePremiumButton(parent);
|
||||
const auto &st = st::premiumPreviewBox.button;
|
||||
result->resize(width, st.height);
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
result.data(),
|
||||
tr::lng_sticker_premium_button(),
|
||||
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());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void StickerBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Descriptor &descriptor,
|
||||
const std::shared_ptr<Data::DocumentMedia> &media,
|
||||
const QImage &back) {
|
||||
const auto size = QSize(
|
||||
st::boxWideWidth,
|
||||
HistoryView::Sticker::UsualPremiumEffectSize().height());
|
||||
box->setWidth(size.width());
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
const auto outer = box->addRow(
|
||||
ChatBackPreview(box, size.height(), back),
|
||||
{});
|
||||
struct State {
|
||||
Ui::RpWidget *content = nullptr;
|
||||
};
|
||||
const auto state = outer->lifetime().make_state<State>();
|
||||
|
||||
switch (descriptor.section) {
|
||||
case PremiumPreview::Stickers:
|
||||
Assert(media != nullptr);
|
||||
state->content = StickerPreview(outer, controller, media);
|
||||
break;
|
||||
case PremiumPreview::Reactions:
|
||||
state->content = ReactionsPreview(outer, controller);
|
||||
break;
|
||||
case PremiumPreview::Avatars:
|
||||
break;
|
||||
}
|
||||
|
||||
const auto padding = st::premiumPreviewAboutPadding;
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_sticker_premium_about(),
|
||||
st::premiumPreviewAbout);
|
||||
label->resizeToWidth(size.width() - padding.left() - padding.right());
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
std::move(label)),
|
||||
padding);
|
||||
box->setStyle(st::premiumPreviewBox);
|
||||
const auto buttonPadding = st::premiumPreviewBox.buttonPadding;
|
||||
const auto width = size.width()
|
||||
- buttonPadding.left()
|
||||
- buttonPadding.right();
|
||||
auto button = CreateUnlockButton(box, width);
|
||||
button->setClickedCallback([=] {
|
||||
Settings::ShowPremium(controller, "premium_stickers");
|
||||
});
|
||||
box->addButton(std::move(button));
|
||||
}
|
||||
|
||||
void Show(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Descriptor &descriptor,
|
||||
const std::shared_ptr<Data::DocumentMedia> &media,
|
||||
QImage back) {
|
||||
controller->show(Box(StickerBox, controller, descriptor, media, back));
|
||||
}
|
||||
|
||||
void Show(not_null<Window::SessionController*> controller, QImage back) {
|
||||
auto &list = Preloads();
|
||||
for (auto i = begin(list); i != end(list);) {
|
||||
const auto already = i->controller.get();
|
||||
if (!already) {
|
||||
i = list.erase(i);
|
||||
} else if (already == controller) {
|
||||
Show(controller, i->descriptor, i->media, back);
|
||||
i = list.erase(i);
|
||||
return;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage SolidColorImage(QSize size, QColor color) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
result.fill(color);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Show(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Descriptor &&descriptor) {
|
||||
auto &list = Preloads();
|
||||
for (auto i = begin(list); i != end(list);) {
|
||||
const auto already = i->controller.get();
|
||||
if (!already) {
|
||||
i = list.erase(i);
|
||||
} else if (already == controller) {
|
||||
if (i->descriptor == descriptor) {
|
||||
return;
|
||||
}
|
||||
i->descriptor = descriptor;
|
||||
i->media = descriptor.requestedSticker
|
||||
? descriptor.requestedSticker->createMediaView()
|
||||
: nullptr;
|
||||
if (const auto &media = i->media) {
|
||||
PreloadSticker(media);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
const auto weak = base::make_weak(controller.get());
|
||||
list.push_back({
|
||||
.descriptor = descriptor,
|
||||
.media = (descriptor.requestedSticker
|
||||
? descriptor.requestedSticker->createMediaView()
|
||||
: nullptr),
|
||||
.controller = weak,
|
||||
});
|
||||
if (const auto &media = list.back().media) {
|
||||
PreloadSticker(media);
|
||||
}
|
||||
|
||||
const auto fill = QSize(st::boxWideWidth, st::boxWideWidth);
|
||||
const auto theme = controller->currentChatTheme();
|
||||
const auto color = theme->background().colorForFill;
|
||||
const auto area = QSize(fill.width(), fill.height() * 2);
|
||||
const auto request = theme->cacheBackgroundRequest(area);
|
||||
crl::async([=] {
|
||||
using Option = Images::Option;
|
||||
auto back = color
|
||||
? SolidColorImage(area, *color)
|
||||
: request.background.waitingForNegativePattern()
|
||||
? SolidColorImage(area, Qt::black)
|
||||
: Ui::CacheBackground(request).image;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
auto cropped = back.copy(QRect(
|
||||
QPoint(0, fill.height() * factor / 2),
|
||||
fill * factor));
|
||||
cropped.setDevicePixelRatio(factor);
|
||||
const auto options = Images::Options()
|
||||
| Option::RoundSkipBottomLeft
|
||||
| Option::RoundSkipBottomRight
|
||||
| Option::RoundLarge;
|
||||
const auto result = Images::Round(
|
||||
std::move(cropped),
|
||||
Images::CornersMask(st::boxRadius),
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
crl::on_main([=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
Show(strong, result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowStickerPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<DocumentData*> document) {
|
||||
Show(controller, Descriptor{
|
||||
.section = PremiumPreview::Stickers,
|
||||
.requestedSticker = document,
|
||||
});
|
||||
}
|
||||
|
||||
void ShowPremiumPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
const base::flat_set<QString> &disabledReactions) {
|
||||
Show(controller, Descriptor{
|
||||
.section = section,
|
||||
.disabledReactions = disabledReactions,
|
||||
});
|
||||
}
|
|
@ -16,3 +16,14 @@ class SessionController;
|
|||
void ShowStickerPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
enum class PremiumPreview {
|
||||
Reactions,
|
||||
Stickers,
|
||||
Avatars,
|
||||
};
|
||||
|
||||
void ShowPremiumPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
const base::flat_set<QString> &disabledReactions = {});
|
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_react_button.h" // DefaultIconFactory
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "settings/settings_premium.h"
|
||||
|
@ -448,7 +449,9 @@ void ReactionsSettingsBox(
|
|||
|
||||
button->setClickedCallback([=, emoji = r.emoji] {
|
||||
if (premium && !controller->session().premium()) {
|
||||
Settings::ShowPremium(controller, "unique_reactions");
|
||||
ShowPremiumPreviewBox(
|
||||
controller,
|
||||
PremiumPreview::Reactions);
|
||||
return;
|
||||
}
|
||||
checkButton(button);
|
||||
|
|
|
@ -1,324 +0,0 @@
|
|||
/*
|
||||
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 "boxes/sticker_preview_box.h"
|
||||
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "chat_helpers/stickers_emoji_pack.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPremiumShift = 0.082;
|
||||
|
||||
struct Preload {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
base::weak_ptr<Window::SessionController> controller;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<Preload> &Preloads() {
|
||||
static auto result = std::vector<Preload>();
|
||||
return result;
|
||||
}
|
||||
|
||||
void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
const auto origin = media->owner()->stickerSetOrigin();
|
||||
media->automaticLoad(origin, nullptr);
|
||||
media->videoThumbnailWanted(origin);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> StickerPreview(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const std::shared_ptr<Data::DocumentMedia> &media,
|
||||
const QImage &back,
|
||||
QSize size) {
|
||||
auto result = object_ptr<Ui::FixedHeightWidget>(parent, size.height());
|
||||
const auto raw = result.data();
|
||||
auto &lifetime = raw->lifetime();
|
||||
|
||||
struct State {
|
||||
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
std::unique_ptr<Lottie::SinglePlayer> effect;
|
||||
std::unique_ptr<Ui::PathShiftGradient> pathGradient;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
using namespace HistoryView;
|
||||
const auto document = media->owner();
|
||||
const auto lottieSize = Sticker::Size(document);
|
||||
const auto effectSize = Sticker::PremiumEffectSize(document);
|
||||
const auto createLottieIfReady = [=] {
|
||||
if (state->lottie) {
|
||||
return;
|
||||
}
|
||||
const auto document = media->owner();
|
||||
const auto sticker = document->sticker();
|
||||
if (!sticker || !sticker->isLottie() || !media->loaded()) {
|
||||
return;
|
||||
} else if (media->videoThumbnailContent().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
state->lottie = ChatHelpers::LottiePlayerFromDocument(
|
||||
media.get(),
|
||||
nullptr,
|
||||
ChatHelpers::StickerLottieSize::MessageHistory,
|
||||
lottieSize * factor,
|
||||
Lottie::Quality::High);
|
||||
state->effect = document->session().emojiStickersPack().effectPlayer(
|
||||
document,
|
||||
media->videoThumbnailContent(),
|
||||
QString(),
|
||||
true);
|
||||
|
||||
const auto update = [=] { raw->update(); };
|
||||
auto &lifetime = raw->lifetime();
|
||||
state->lottie->updates() | rpl::start_with_next(update, lifetime);
|
||||
state->effect->updates() | rpl::start_with_next(update, lifetime);
|
||||
};
|
||||
state->pathGradient = MakePathShiftGradient(
|
||||
controller->chatStyle(),
|
||||
[=] { raw->update(); });
|
||||
|
||||
raw->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
createLottieIfReady();
|
||||
|
||||
auto p = QPainter(raw);
|
||||
p.drawImage(0, 0, back);
|
||||
|
||||
const auto zero = (size.width() - effectSize.width()) / 2;
|
||||
const auto left = zero
|
||||
+ effectSize.width()
|
||||
- int(lottieSize.width() * (1. + kPremiumShift));
|
||||
const auto top = (effectSize.height() - lottieSize.height()) / 2;
|
||||
const auto r = QRect(QPoint(left, top), lottieSize);
|
||||
if (!state->lottie
|
||||
|| !state->lottie->ready()
|
||||
|| !state->effect->ready()) {
|
||||
p.setBrush(controller->chatStyle()->msgServiceBg());
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
r,
|
||||
state->pathGradient.get());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto frame = state->lottie->frameInfo({ lottieSize * factor });
|
||||
const auto effect = state->effect->frameInfo(
|
||||
{ effectSize * factor });
|
||||
//const auto framesCount = !frame.image.isNull()
|
||||
// ? state->lottie->framesCount()
|
||||
// : 1;
|
||||
//const auto effectsCount = !effect.image.isNull()
|
||||
// ? state->effect->framesCount()
|
||||
// : 1;
|
||||
|
||||
p.drawImage(r, frame.image);
|
||||
p.drawImage(QRect(QPoint(zero, 0), effectSize), effect.image);
|
||||
|
||||
if (!frame.image.isNull()/*
|
||||
&& ((frame.index % effectsCount) <= effect.index)*/) {
|
||||
state->lottie->markFrameShown();
|
||||
}
|
||||
if (!effect.image.isNull()/*
|
||||
&& ((effect.index % framesCount) <= frame.index)*/) {
|
||||
state->effect->markFrameShown();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::AbstractButton> CreateGradientButton(
|
||||
QWidget *parent,
|
||||
QGradientStops stops) {
|
||||
return object_ptr<Ui::GradientButton>(parent, std::move(stops));
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::AbstractButton> CreatePremiumButton(
|
||||
QWidget *parent) {
|
||||
return CreateGradientButton(parent, Ui::Premium::ButtonGradientStops());
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::AbstractButton> CreateUnlockButton(
|
||||
QWidget *parent,
|
||||
int width) {
|
||||
auto result = CreatePremiumButton(parent);
|
||||
const auto &st = st::premiumPreviewBox.button;
|
||||
result->resize(width, st.height);
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
result.data(),
|
||||
tr::lng_sticker_premium_button(),
|
||||
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());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void StickerBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const std::shared_ptr<Data::DocumentMedia> &media,
|
||||
const QImage &back) {
|
||||
const auto size = QSize(
|
||||
st::boxWideWidth,
|
||||
HistoryView::Sticker::PremiumEffectSize(media->owner()).height());
|
||||
box->setWidth(size.width());
|
||||
box->setNoContentMargin(true);
|
||||
box->addRow(StickerPreview(box, controller, media, back, size), {});
|
||||
const auto padding = st::premiumPreviewAboutPadding;
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_sticker_premium_about(),
|
||||
st::premiumPreviewAbout);
|
||||
label->resizeToWidth(size.width() - padding.left() - padding.right());
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
std::move(label)),
|
||||
padding);
|
||||
box->setStyle(st::premiumPreviewBox);
|
||||
const auto buttonPadding = st::premiumPreviewBox.buttonPadding;
|
||||
const auto width = size.width()
|
||||
- buttonPadding.left()
|
||||
- buttonPadding.right();
|
||||
auto button = CreateUnlockButton(box, width);
|
||||
button->setClickedCallback([=] {
|
||||
Settings::ShowPremium(controller, "premium_stickers");
|
||||
});
|
||||
box->addButton(std::move(button));
|
||||
}
|
||||
|
||||
void Show(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const std::shared_ptr<Data::DocumentMedia> &media,
|
||||
QImage back) {
|
||||
controller->show(Box(StickerBox, controller, media, back));
|
||||
}
|
||||
|
||||
void Show(not_null<Window::SessionController*> controller, QImage back) {
|
||||
auto &list = Preloads();
|
||||
for (auto i = begin(list); i != end(list);) {
|
||||
const auto already = i->controller.get();
|
||||
if (!already) {
|
||||
i = list.erase(i);
|
||||
} else if (already == controller) {
|
||||
Show(controller, i->media, back);
|
||||
i = list.erase(i);
|
||||
return;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage SolidColorImage(QSize size, QColor color) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
result.fill(color);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowStickerPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<DocumentData*> document) {
|
||||
auto &list = Preloads();
|
||||
for (auto i = begin(list); i != end(list);) {
|
||||
const auto already = i->controller.get();
|
||||
if (!already) {
|
||||
i = list.erase(i);
|
||||
} else if (already == controller) {
|
||||
if (i->document == document) {
|
||||
return;
|
||||
}
|
||||
i->document = document;
|
||||
i->media = document->createMediaView();
|
||||
PreloadSticker(i->media);
|
||||
return;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
const auto weak = base::make_weak(controller.get());
|
||||
list.push_back({
|
||||
.document = document,
|
||||
.media = document->createMediaView(),
|
||||
.controller = weak,
|
||||
});
|
||||
PreloadSticker(list.back().media);
|
||||
|
||||
const auto fill = QSize(st::boxWideWidth, st::boxWideWidth);
|
||||
const auto theme = controller->currentChatTheme();
|
||||
const auto color = theme->background().colorForFill;
|
||||
const auto area = QSize(fill.width(), fill.height() * 2);
|
||||
const auto request = theme->cacheBackgroundRequest(area);
|
||||
crl::async([=] {
|
||||
using Option = Images::Option;
|
||||
auto back = color
|
||||
? SolidColorImage(area, *color)
|
||||
: request.background.waitingForNegativePattern()
|
||||
? SolidColorImage(area, Qt::black)
|
||||
: Ui::CacheBackground(request).image;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
auto cropped = back.copy(QRect(
|
||||
QPoint(0, fill.height() * factor / 2),
|
||||
fill * factor));
|
||||
cropped.setDevicePixelRatio(factor);
|
||||
const auto options = Images::Options()
|
||||
| Option::RoundSkipBottomLeft
|
||||
| Option::RoundSkipBottomRight
|
||||
| Option::RoundLarge;
|
||||
const auto result = Images::Round(
|
||||
std::move(cropped),
|
||||
Images::CornersMask(st::boxRadius),
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
crl::on_main([=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
Show(strong, result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -300,3 +300,14 @@ premiumPreviewButtonLabel: FlatLabel(defaultFlatLabel) {
|
|||
}
|
||||
|
||||
stickersPremiumLock: icon{{ "emoji/premium_lock", premiumButtonFg }};
|
||||
|
||||
premiumReactionSize: 152px;
|
||||
premiumReactionAround: 192px;
|
||||
premiumReactionTop: 108px;
|
||||
premiumReactionSkip1: 96px;
|
||||
premiumReactionScale1: 0.78;
|
||||
premiumReactionSkip2: 78px;
|
||||
premiumReactionScale2: 0.62;
|
||||
premiumReactionSkip3: 64px;
|
||||
premiumReactionScale3: 0.5;
|
||||
|
||||
|
|
|
@ -60,6 +60,11 @@ enum class StickerLottieSize : uchar {
|
|||
EmojiInteractionReserved1,
|
||||
EmojiInteractionReserved2,
|
||||
EmojiInteractionReserved3,
|
||||
EmojiInteractionReserved4,
|
||||
EmojiInteractionReserved5,
|
||||
EmojiInteractionReserved6,
|
||||
EmojiInteractionReserved7,
|
||||
PremiumReactionPreview,
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
|
|
|
@ -336,7 +336,6 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
|||
}
|
||||
const auto selectAnimation = _owner->processDocument(
|
||||
data.vselect_animation());
|
||||
static auto test = 0; AssertIsDebug();
|
||||
return known
|
||||
? std::make_optional(Reaction{
|
||||
.emoji = emoji,
|
||||
|
@ -357,7 +356,7 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
|||
*data.varound_animation()).get()
|
||||
: nullptr),
|
||||
.active = !data.is_inactive(),
|
||||
.premium = (data.is_premium() || ((++test) % 2)),
|
||||
.premium = data.is_premium(),
|
||||
})
|
||||
: std::nullopt;
|
||||
});
|
||||
|
|
|
@ -344,7 +344,11 @@ void HistoryMessageReply::updateName(
|
|||
not_null<HistoryMessage*> holder) const {
|
||||
if (const auto name = replyToFromName(holder); !name.isEmpty()) {
|
||||
replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
|
||||
if (const auto from = replyToFrom(holder)) {
|
||||
replyToVersion = from->nameVersion;
|
||||
} else {
|
||||
replyToVersion = replyToMsg->author()->nameVersion;
|
||||
}
|
||||
bool hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
|
||||
int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
||||
int32 w = replyToName.maxWidth();
|
||||
|
|
|
@ -155,6 +155,11 @@ QSize Sticker::PremiumEffectSize(not_null<DocumentData*> document) {
|
|||
return Size(document) * kPremiumMultiplier;
|
||||
}
|
||||
|
||||
QSize Sticker::UsualPremiumEffectSize() {
|
||||
return DownscaledSize({ kMaxSizeFixed, kMaxSizeFixed }, Size())
|
||||
* kPremiumMultiplier;
|
||||
}
|
||||
|
||||
QSize Sticker::EmojiEffectSize() {
|
||||
return EmojiSize() * kEmojiMultiplier;
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ public:
|
|||
[[nodiscard]] static QSize Size(not_null<DocumentData*> document);
|
||||
[[nodiscard]] static QSize PremiumEffectSize(
|
||||
not_null<DocumentData*> document);
|
||||
[[nodiscard]] static QSize UsualPremiumEffectSize();
|
||||
[[nodiscard]] static QSize EmojiEffectSize();
|
||||
[[nodiscard]] static QSize EmojiSize();
|
||||
[[nodiscard]] static ClickHandlerPtr ShowSetHandler(
|
||||
|
|
|
@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/ui_utility.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "boxes/sticker_preview_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_document.h"
|
||||
|
@ -355,7 +355,10 @@ bool ShowReactPremiumError(
|
|||
if (i == end(list) || !i->premium) {
|
||||
return false;
|
||||
}
|
||||
Settings::ShowPremium(controller, "unique_reactions");
|
||||
ShowPremiumPreviewBox(
|
||||
controller,
|
||||
PremiumPreview::Reactions,
|
||||
{});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue