mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 22:54:01 +02:00
Play premium sticker effects.
This commit is contained in:
parent
d87c9c72fb
commit
935fb79c52
15 changed files with 168 additions and 40 deletions
|
@ -254,6 +254,7 @@ void DocumentMedia::videoThumbnailWanted(Data::FileOrigin origin) {
|
|||
|
||||
void DocumentMedia::setVideoThumbnail(QByteArray content) {
|
||||
_videoThumbnailBytes = std::move(content);
|
||||
_videoThumbnailBytes.detach();
|
||||
}
|
||||
|
||||
void DocumentMedia::checkStickerLarge() {
|
||||
|
|
|
@ -414,6 +414,7 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
|
|||
| Flag::Scam
|
||||
| Flag::Fake
|
||||
| Flag::BotInlineGeo
|
||||
| Flag::Premium
|
||||
| Flag::Support
|
||||
| (!minimal
|
||||
? Flag::Contact
|
||||
|
@ -2827,7 +2828,7 @@ void Session::documentApplyFields(
|
|||
? Images::FromVideoSize(_session, data, *videoThumbnailSize)
|
||||
: ImageWithLocation();
|
||||
const auto isPremiumSticker = videoThumbnailSize
|
||||
&& (videoThumbnailSize->c_videoSize().vtype().v == "fp");
|
||||
&& (videoThumbnailSize->c_videoSize().vtype().v == "f");
|
||||
documentApplyFields(
|
||||
document,
|
||||
data.vaccess_hash().v,
|
||||
|
|
|
@ -673,6 +673,9 @@ void InnerWidget::elementReplyTo(const FullMsgId &to) {
|
|||
void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementStartPremium(not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementShowSpoilerAnimation() {
|
||||
_spoilerOpacity.stop();
|
||||
_spoilerOpacity.start([=] { update(); }, 0., 1., st::fadeWrapDuration);
|
||||
|
|
|
@ -138,6 +138,8 @@ public:
|
|||
void elementReplyTo(const FullMsgId &to) override;
|
||||
void elementStartInteraction(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
void elementStartPremium(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
void elementShowSpoilerAnimation() override;
|
||||
|
||||
~InnerWidget();
|
||||
|
|
|
@ -254,6 +254,11 @@ public:
|
|||
_widget->elementStartInteraction(view);
|
||||
}
|
||||
}
|
||||
void elementStartPremium(not_null<const Element*> view) override {
|
||||
if (_widget) {
|
||||
_widget->elementStartPremium(view);
|
||||
}
|
||||
}
|
||||
void elementShowSpoilerAnimation() override {
|
||||
if (_widget) {
|
||||
_widget->elementShowSpoilerAnimation();
|
||||
|
@ -3171,6 +3176,11 @@ void HistoryInner::elementStartInteraction(not_null<const Element*> view) {
|
|||
_controller->emojiInteractions().startOutgoing(view);
|
||||
}
|
||||
|
||||
void HistoryInner::elementStartPremium(not_null<const Element*> view) {
|
||||
_emojiInteractions->playPremiumEffect(view);
|
||||
_animatedStickersPlayed.emplace(view->data());
|
||||
}
|
||||
|
||||
void HistoryInner::elementShowSpoilerAnimation() {
|
||||
_spoilerOpacity.stop();
|
||||
_spoilerOpacity.start([=] { update(); }, 0., 1., st::fadeWrapDuration);
|
||||
|
|
|
@ -145,6 +145,7 @@ public:
|
|||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient();
|
||||
void elementReplyTo(const FullMsgId &to);
|
||||
void elementStartInteraction(not_null<const Element*> view);
|
||||
void elementStartPremium(not_null<const Element*> view);
|
||||
void elementShowSpoilerAnimation();
|
||||
|
||||
void updateBotInfo(bool recount = true);
|
||||
|
|
|
@ -205,6 +205,10 @@ void SimpleElementDelegate::elementStartInteraction(
|
|||
not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void SimpleElementDelegate::elementStartPremium(
|
||||
not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void SimpleElementDelegate::elementShowSpoilerAnimation() {
|
||||
}
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ public:
|
|||
virtual not_null<Ui::PathShiftGradient*> elementPathShiftGradient() = 0;
|
||||
virtual void elementReplyTo(const FullMsgId &to) = 0;
|
||||
virtual void elementStartInteraction(not_null<const Element*> view) = 0;
|
||||
virtual void elementStartPremium(not_null<const Element*> view) = 0;
|
||||
virtual void elementShowSpoilerAnimation() = 0;
|
||||
|
||||
virtual ~ElementDelegate() {
|
||||
|
@ -162,6 +163,7 @@ public:
|
|||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||
void elementReplyTo(const FullMsgId &to) override;
|
||||
void elementStartInteraction(not_null<const Element*> view) override;
|
||||
void elementStartPremium(not_null<const Element*> view) override;
|
||||
void elementShowSpoilerAnimation() override;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -24,8 +24,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSizeMultiplier = 3;
|
||||
constexpr auto kCachesCount = 4;
|
||||
constexpr auto kEmojiMultiplier = 3;
|
||||
constexpr auto kPremiumMultiplier = 2.25;
|
||||
constexpr auto kEmojiCachesCount = 4;
|
||||
constexpr auto kPremiumCachesCount = 8;
|
||||
constexpr auto kMaxPlays = 5;
|
||||
constexpr auto kMaxPlaysWithSmallDelay = 3;
|
||||
constexpr auto kSmallDelay = crl::time(200);
|
||||
|
@ -55,6 +57,7 @@ EmojiInteractions::EmojiInteractions(not_null<Main::Session*> session)
|
|||
}, _lifetime);
|
||||
|
||||
_emojiSize = Sticker::EmojiSize();
|
||||
_premiumSize = Sticker::Size();
|
||||
}
|
||||
|
||||
EmojiInteractions::~EmojiInteractions() = default;
|
||||
|
@ -84,27 +87,63 @@ void EmojiInteractions::play(
|
|||
}
|
||||
}
|
||||
|
||||
void EmojiInteractions::playPremiumEffect(not_null<const Element*> view) {
|
||||
if (const auto media = view->media()) {
|
||||
if (const auto document = media->getDocument()) {
|
||||
if (document->isPremiumSticker()) {
|
||||
play(
|
||||
QString(),
|
||||
view,
|
||||
document,
|
||||
document->createMediaView()->videoThumbnailContent(),
|
||||
QString(),
|
||||
false,
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiInteractions::play(
|
||||
QString emoticon,
|
||||
not_null<Element*> view,
|
||||
not_null<const Element*> view,
|
||||
std::shared_ptr<Data::DocumentMedia> media,
|
||||
bool incoming) {
|
||||
play(
|
||||
std::move(emoticon),
|
||||
view,
|
||||
media->owner(),
|
||||
media->bytes(),
|
||||
media->owner()->filepath(),
|
||||
incoming,
|
||||
false);
|
||||
}
|
||||
|
||||
void EmojiInteractions::play(
|
||||
QString emoticon,
|
||||
not_null<const Element*> view,
|
||||
not_null<DocumentData*> document,
|
||||
QByteArray data,
|
||||
QString filepath,
|
||||
bool incoming,
|
||||
bool premium) {
|
||||
const auto top = view->block()->y() + view->y();
|
||||
const auto bottom = top + view->height();
|
||||
if (_visibleTop >= bottom
|
||||
|| _visibleBottom <= top
|
||||
|| _visibleTop == _visibleBottom) {
|
||||
|| _visibleTop == _visibleBottom
|
||||
|| (data.isEmpty() && filepath.isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lottie = preparePlayer(media.get());
|
||||
auto lottie = preparePlayer(document, data, filepath, premium);
|
||||
|
||||
const auto shift = GenerateRandomShift(_emojiSize);
|
||||
const auto shift = premium ? QPoint() : GenerateRandomShift(_emojiSize);
|
||||
lottie->updates(
|
||||
) | rpl::start_with_next([=](Lottie::Update update) {
|
||||
v::match(update.data, [&](const Lottie::Information &information) {
|
||||
}, [&](const Lottie::DisplayFrameRequest &request) {
|
||||
const auto rect = computeRect(view).translated(shift);
|
||||
const auto rect = computeRect(view, premium).translated(shift);
|
||||
if (rect.y() + rect.height() >= _visibleTop
|
||||
&& rect.y() <= _visibleBottom) {
|
||||
_updateRequests.fire_copy(rect);
|
||||
|
@ -115,19 +154,30 @@ void EmojiInteractions::play(
|
|||
.view = view,
|
||||
.lottie = std::move(lottie),
|
||||
.shift = shift,
|
||||
.premium = premium,
|
||||
});
|
||||
if (incoming) {
|
||||
_playStarted.fire(std::move(emoticon));
|
||||
}
|
||||
if (const auto media = view->media()) {
|
||||
media->stickerClearLoopPlayed();
|
||||
if (!premium) {
|
||||
media->stickerClearLoopPlayed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QSize EmojiInteractions::sizeFor(bool premium) const {
|
||||
return premium
|
||||
? (_premiumSize * kPremiumMultiplier)
|
||||
: (_emojiSize * kEmojiMultiplier);
|
||||
}
|
||||
|
||||
std::unique_ptr<Lottie::SinglePlayer> EmojiInteractions::preparePlayer(
|
||||
not_null<Data::DocumentMedia*> media) {
|
||||
not_null<DocumentData*> document,
|
||||
QByteArray data,
|
||||
QString filepath,
|
||||
bool premium) {
|
||||
// Shortened copy from stickers_lottie module.
|
||||
const auto document = media->owner();
|
||||
const auto baseKey = document->bigFileBaseCacheKey();
|
||||
const auto tag = uint8(0);
|
||||
const auto keyShift = ((tag << 4) & 0xF0)
|
||||
|
@ -149,10 +199,8 @@ std::unique_ptr<Lottie::SinglePlayer> EmojiInteractions::preparePlayer(
|
|||
std::move(data));
|
||||
});
|
||||
};
|
||||
const auto data = media->bytes();
|
||||
const auto filepath = document->filepath();
|
||||
const auto request = Lottie::FrameRequest{
|
||||
_emojiSize * kSizeMultiplier * style::DevicePixelRatio(),
|
||||
sizeFor(premium) * style::DevicePixelRatio(),
|
||||
};
|
||||
auto &weakProvider = _sharedProviders[document];
|
||||
auto shared = [&] {
|
||||
|
@ -160,7 +208,7 @@ std::unique_ptr<Lottie::SinglePlayer> EmojiInteractions::preparePlayer(
|
|||
return result;
|
||||
}
|
||||
const auto result = Lottie::SinglePlayer::SharedProvider(
|
||||
kCachesCount,
|
||||
premium ? kPremiumCachesCount : kEmojiCachesCount,
|
||||
get,
|
||||
put,
|
||||
Lottie::ReadContent(data, filepath),
|
||||
|
@ -179,19 +227,23 @@ void EmojiInteractions::visibleAreaUpdated(
|
|||
_visibleBottom = visibleBottom;
|
||||
}
|
||||
|
||||
QRect EmojiInteractions::computeRect(not_null<Element*> view) const {
|
||||
QRect EmojiInteractions::computeRect(
|
||||
not_null<const Element*> view,
|
||||
bool premium) const {
|
||||
const auto fullWidth = view->width();
|
||||
const auto shift = (_emojiSize.width() * kSizeMultiplier) / 40;
|
||||
const auto sticker = premium ? _premiumSize : _emojiSize;
|
||||
const auto size = sizeFor(premium);
|
||||
const auto shift = size.width() / 40;
|
||||
const auto skip = (view->hasFromPhoto() ? st::msgPhotoSkip : 0)
|
||||
+ st::msgMargin.left();
|
||||
const auto rightAligned = view->hasOutLayout()
|
||||
&& !view->delegate()->elementIsChatWide();
|
||||
const auto left = rightAligned
|
||||
? (fullWidth - skip + shift - _emojiSize.width() * kSizeMultiplier)
|
||||
? (fullWidth - skip + shift - size.width())
|
||||
: (skip - shift);
|
||||
const auto viewTop = view->block()->y() + view->y() + view->marginTop();
|
||||
const auto top = viewTop - _emojiSize.height();
|
||||
return QRect(QPoint(left, top), _emojiSize * kSizeMultiplier);
|
||||
const auto top = viewTop + (sticker.height() - size.height()) / 2;
|
||||
return QRect(QPoint(left, top), size);
|
||||
}
|
||||
|
||||
void EmojiInteractions::paint(QPainter &p) {
|
||||
|
@ -201,7 +253,7 @@ void EmojiInteractions::paint(QPainter &p) {
|
|||
continue;
|
||||
}
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = _emojiSize * kSizeMultiplier * factor;
|
||||
request.box = sizeFor(play.premium) * factor;
|
||||
const auto rightAligned = play.view->hasOutLayout()
|
||||
&& !play.view->delegate()->elementIsChatWide();
|
||||
if (!rightAligned) {
|
||||
|
@ -217,7 +269,7 @@ void EmojiInteractions::paint(QPainter &p) {
|
|||
if (play.frame + 1 == play.framesCount) {
|
||||
play.finished = true;
|
||||
}
|
||||
const auto rect = computeRect(play.view);
|
||||
const auto rect = computeRect(play.view, play.premium);
|
||||
p.drawImage(
|
||||
QRect(rect.topLeft() + play.shift, frame.image.size() / factor),
|
||||
frame.image);
|
||||
|
|
|
@ -36,6 +36,7 @@ public:
|
|||
void play(
|
||||
ChatHelpers::EmojiInteractionPlayRequest request,
|
||||
not_null<Element*> view);
|
||||
void playPremiumEffect(not_null<const Element*> view);
|
||||
void visibleAreaUpdated(int visibleTop, int visibleBottom);
|
||||
|
||||
void paint(QPainter &p);
|
||||
|
@ -44,39 +45,55 @@ public:
|
|||
|
||||
private:
|
||||
struct Play {
|
||||
not_null<Element*> view;
|
||||
not_null<const Element*> view;
|
||||
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
QPoint shift;
|
||||
int frame = 0;
|
||||
int framesCount = 0;
|
||||
int frameRate = 0;
|
||||
bool premium = false;
|
||||
bool finished = false;
|
||||
};
|
||||
struct Delayed {
|
||||
QString emoticon;
|
||||
not_null<Element*> view;
|
||||
not_null<const Element*> view;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
crl::time shouldHaveStartedAt = 0;
|
||||
bool incoming = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] QRect computeRect(not_null<Element*> view) const;
|
||||
[[nodiscard]] QRect computeRect(
|
||||
not_null<const Element*> view,
|
||||
bool premium) const;
|
||||
|
||||
void play(
|
||||
QString emoticon,
|
||||
not_null<Element*> view,
|
||||
not_null<const Element*> view,
|
||||
std::shared_ptr<Data::DocumentMedia> media,
|
||||
bool incoming);
|
||||
void play(
|
||||
QString emoticon,
|
||||
not_null<const Element*> view,
|
||||
not_null<DocumentData*> document,
|
||||
QByteArray data,
|
||||
QString filepath,
|
||||
bool incoming,
|
||||
bool premium);
|
||||
void checkDelayed();
|
||||
|
||||
[[nodiscard]] QSize sizeFor(bool premium) const;
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> preparePlayer(
|
||||
not_null<Data::DocumentMedia*> media);
|
||||
not_null<DocumentData*> document,
|
||||
QByteArray data,
|
||||
QString filepath,
|
||||
bool premium);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
int _visibleTop = 0;
|
||||
int _visibleBottom = 0;
|
||||
QSize _emojiSize;
|
||||
QSize _premiumSize;
|
||||
|
||||
std::vector<Play> _plays;
|
||||
std::vector<Delayed> _delayed;
|
||||
|
|
|
@ -1519,6 +1519,9 @@ void ListWidget::elementReplyTo(const FullMsgId &to) {
|
|||
void ListWidget::elementStartInteraction(not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void ListWidget::elementStartPremium(not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void ListWidget::elementShowSpoilerAnimation() {
|
||||
_spoilerOpacity.stop();
|
||||
_spoilerOpacity.start([=] { update(); }, 0., 1., st::fadeWrapDuration);
|
||||
|
|
|
@ -292,6 +292,7 @@ public:
|
|||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||
void elementReplyTo(const FullMsgId &to) override;
|
||||
void elementStartInteraction(not_null<const Element*> view) override;
|
||||
void elementStartPremium(not_null<const Element*> view) override;
|
||||
void elementShowSpoilerAnimation() override;
|
||||
|
||||
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);
|
||||
|
|
|
@ -68,6 +68,9 @@ Sticker::Sticker(
|
|||
dataMediaCreated();
|
||||
} else {
|
||||
_data->loadThumbnail(parent->data()->fullId());
|
||||
if (_data->isPremiumSticker()) {
|
||||
_data->loadVideoThumbnail(parent->data()->fullId());
|
||||
}
|
||||
}
|
||||
if (const auto media = replacing ? replacing->media() : nullptr) {
|
||||
_lottie = media->stickerTakeLottie(_data, _replacements);
|
||||
|
@ -123,7 +126,9 @@ bool Sticker::readyToDrawLottie() {
|
|||
ensureDataMediaCreated();
|
||||
_dataMedia->checkStickerLarge();
|
||||
const auto loaded = _dataMedia->loaded();
|
||||
if (sticker->isLottie() && !_lottie && loaded) {
|
||||
const auto waitingForPremium = _data->isPremiumSticker()
|
||||
&& _dataMedia->videoThumbnailContent().isEmpty();
|
||||
if (sticker->isLottie() && !_lottie && loaded && !waitingForPremium) {
|
||||
setupLottie();
|
||||
}
|
||||
return (_lottie && _lottie->ready());
|
||||
|
@ -153,6 +158,19 @@ void Sticker::draw(
|
|||
}
|
||||
}
|
||||
|
||||
ClickHandlerPtr Sticker::link() {
|
||||
return _link;
|
||||
}
|
||||
|
||||
DocumentData *Sticker::document() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
void Sticker::stickerClearLoopPlayed() {
|
||||
_lottieOncePlayed = false;
|
||||
_premiumEffectPlayed = false;
|
||||
}
|
||||
|
||||
void Sticker::paintLottie(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
|
@ -162,6 +180,14 @@ void Sticker::paintLottie(
|
|||
if (context.selected() && !_nextLastDiceFrame) {
|
||||
request.colored = context.st->msgStickerOverlay()->c;
|
||||
}
|
||||
const auto premium = _data->isPremiumSticker();
|
||||
if (premium) {
|
||||
const auto rightAligned = _parent->hasOutLayout()
|
||||
&& !_parent->delegate()->elementIsChatWide();
|
||||
if (!rightAligned) {
|
||||
request.mirrorHorizontal = true;
|
||||
}
|
||||
}
|
||||
const auto frame = _lottie
|
||||
? _lottie->frameInfo(request)
|
||||
: Lottie::Animation::FrameInfo();
|
||||
|
@ -201,10 +227,10 @@ void Sticker::paintLottie(
|
|||
_framesCount = count;
|
||||
_nextLastDiceFrame = !paused
|
||||
&& (_diceIndex > 0)
|
||||
&& (frame.index + 2 == count);
|
||||
&& (_frameIndex + 2 == count);
|
||||
const auto lastDiceFrame = (_diceIndex > 0) && atTheEnd();
|
||||
const auto switchToNext = !playOnce
|
||||
|| (!lastDiceFrame && (frame.index != 0 || !_lottieOncePlayed));
|
||||
|| (!lastDiceFrame && (_frameIndex != 0 || !_lottieOncePlayed));
|
||||
if (!paused
|
||||
&& switchToNext
|
||||
&& _lottie->markFrameShown()
|
||||
|
@ -338,6 +364,9 @@ void Sticker::dataMediaCreated() const {
|
|||
if (_dataMedia->thumbnailPath().isEmpty()) {
|
||||
_dataMedia->thumbnailWanted(_parent->data()->fullId());
|
||||
}
|
||||
if (_data->isPremiumSticker()) {
|
||||
_data->loadVideoThumbnail(_parent->data()->fullId());
|
||||
}
|
||||
_parent->history()->owner().registerHeavyViewPart(_parent);
|
||||
}
|
||||
|
||||
|
@ -355,6 +384,11 @@ void Sticker::setupLottie() {
|
|||
ChatHelpers::StickerLottieSize::MessageHistory,
|
||||
size() * cIntRetinaFactor(),
|
||||
Lottie::Quality::High);
|
||||
if (_data->isPremiumSticker()
|
||||
&& !_premiumEffectPlayed) {
|
||||
_premiumEffectPlayed = true;
|
||||
_parent->delegate()->elementStartPremium(_parent);
|
||||
}
|
||||
lottieCreated();
|
||||
}
|
||||
|
||||
|
|
|
@ -43,16 +43,10 @@ public:
|
|||
Painter &p,
|
||||
const PaintContext &context,
|
||||
const QRect &r) override;
|
||||
ClickHandlerPtr link() override {
|
||||
return _link;
|
||||
}
|
||||
ClickHandlerPtr link() override;
|
||||
|
||||
DocumentData *document() override {
|
||||
return _data;
|
||||
}
|
||||
void stickerClearLoopPlayed() override {
|
||||
_lottieOncePlayed = false;
|
||||
}
|
||||
DocumentData *document() override;
|
||||
void stickerClearLoopPlayed() override;
|
||||
std::unique_ptr<Lottie::SinglePlayer> stickerTakeLottie(
|
||||
not_null<DocumentData*> data,
|
||||
const Lottie::ColorReplacements *replacements) override;
|
||||
|
@ -111,6 +105,7 @@ private:
|
|||
mutable int _frameIndex = -1;
|
||||
mutable int _framesCount = -1;
|
||||
mutable bool _lottieOncePlayed = false;
|
||||
mutable bool _premiumEffectPlayed = false;
|
||||
mutable bool _nextLastDiceFrame = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
|
|
@ -45,7 +45,9 @@ void Document::writeToStream(QDataStream &stream, DocumentData *document) {
|
|||
}
|
||||
}
|
||||
stream << qint32(document->getDuration());
|
||||
stream << qint32(document->isPremiumSticker() ? 1 : 0);
|
||||
if (document->type == StickerDocument) {
|
||||
stream << qint32(document->isPremiumSticker() ? 1 : 0);
|
||||
}
|
||||
writeImageLocation(stream, document->thumbnailLocation());
|
||||
stream << qint32(document->thumbnailByteSize());
|
||||
writeImageLocation(stream, document->videoThumbnailLocation());
|
||||
|
|
Loading…
Add table
Reference in a new issue