Improve premium sticker sync / preview.

This commit is contained in:
John Preston 2022-05-18 14:18:17 +04:00
parent 3b5ec78f4f
commit ca731968ca
26 changed files with 492 additions and 54 deletions

View File

@ -272,6 +272,8 @@ PRIVATE
boxes/sessions_box.h boxes/sessions_box.h
boxes/share_box.cpp boxes/share_box.cpp
boxes/share_box.h boxes/share_box.h
boxes/sticker_preview_box.cpp
boxes/sticker_preview_box.h
boxes/sticker_set_box.cpp boxes/sticker_set_box.cpp
boxes/sticker_set_box.h boxes/sticker_set_box.h
boxes/stickers_box.cpp boxes/stickers_box.cpp

View File

@ -224,6 +224,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_limits_increase" = "Increase Limit"; "lng_limits_increase" = "Increase Limit";
"lng_sticker_premium_about" = "Unlock this sticker and more by subscribing to\nTelegram Premium.";
"lng_sticker_premium_button" = "Unlock Premium Stickers";
"lng_flood_error" = "Too many tries. Please try again later."; "lng_flood_error" = "Too many tries. Please try again later.";
"lng_gif_error" = "An error has occurred while reading GIF animation :("; "lng_gif_error" = "An error has occurred while reading GIF animation :(";
"lng_edit_error" = "You cannot edit this message"; "lng_edit_error" = "You cannot edit this message";

View File

@ -627,6 +627,7 @@ messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity;
messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity;
messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity;
messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity; messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity;
messageEntityAnimatedEmoji#5eef0214 offset:int length:int = MessageEntity;
inputChannelEmpty#ee8c1e86 = InputChannel; inputChannelEmpty#ee8c1e86 = InputChannel;
inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel;

View File

@ -0,0 +1,343 @@
/*
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 "data/data_file_origin.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "lang/lang_keys.h"
#include "ui/chat/chat_theme.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/padding_wrap.h"
#include "lottie/lottie_single_player.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 kPremiumMultiplier = 1.5;
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,
const std::shared_ptr<Data::DocumentMedia> &media,
const QImage &back,
int size) {
auto result = object_ptr<Ui::FixedHeightWidget>(parent, size);
const auto raw = result.data();
auto &lifetime = raw->lifetime();
struct State {
std::unique_ptr<Lottie::SinglePlayer> lottie;
std::unique_ptr<Lottie::SinglePlayer> effect;
};
const auto state = lifetime.make_state<State>();
const auto lottie = int(size / kPremiumMultiplier);
const auto lottieSize = QSize(lottie, lottie);
const auto effectSize = QSize(size, size);
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 = std::make_unique<Lottie::SinglePlayer>(
Lottie::ReadContent(media->bytes(), document->filepath()),
Lottie::FrameRequest{ lottieSize * factor },
Lottie::Quality::High);
state->effect = std::make_unique<Lottie::SinglePlayer>(
Lottie::ReadContent(media->videoThumbnailContent(), {}),
Lottie::FrameRequest{ effectSize * factor },
Lottie::Quality::High);
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);
};
raw->paintRequest(
) | rpl::start_with_next([=] {
createLottieIfReady();
auto p = QPainter(raw);
p.drawImage(0, 0, back);
if (!state->lottie
|| !state->lottie->ready()
|| !state->effect->ready()) {
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;
const auto left = effectSize.width()
- int(lottieSize.width() * (1. + kPremiumShift));
const auto top = (effectSize.height() - lottieSize.height()) / 2;
p.drawImage(
QRect(QPoint(left, top), lottieSize),
state->lottie->frame());
p.drawImage(raw->rect(), state->effect->frame());
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 GradientButton final : public Ui::RippleButton {
public:
GradientButton(QWidget *widget, QGradientStops stops);
private:
void paintEvent(QPaintEvent *e);
void validateBg();
QGradientStops _stops;
QImage _bg;
};
GradientButton::GradientButton(QWidget *widget, QGradientStops stops)
: RippleButton(widget, st::defaultRippleAnimation)
, _stops(std::move(stops)) {
}
void GradientButton::paintEvent(QPaintEvent *e) {
QPainter p(this);
validateBg();
p.drawImage(0, 0, _bg);
const auto ripple = QColor(0, 0, 0, 36);
paintRipple(p, 0, 0, &ripple);
}
void GradientButton::validateBg() {
const auto factor = devicePixelRatio();
if (!_bg.isNull()
&& (_bg.devicePixelRatio() == factor)
&& (_bg.size() == size() * factor)) {
return;
}
_bg = QImage(size() * factor, QImage::Format_ARGB32_Premultiplied);
_bg.setDevicePixelRatio(factor);
auto p = QPainter(&_bg);
auto gradient = QLinearGradient(QPointF(0, 0), QPointF(width(), 0));
gradient.setStops(_stops);
p.fillRect(rect(), gradient);
p.end();
_bg = Images::Round(std::move(_bg), ImageRoundRadius::Large);
}
[[nodiscard]] object_ptr<Ui::AbstractButton> CreateGradientButton(
QWidget *parent,
QGradientStops stops) {
return object_ptr<GradientButton>(parent, std::move(stops));
}
[[nodiscard]] object_ptr<Ui::AbstractButton> CreatePremiumButton(
QWidget *parent) {
return CreateGradientButton(parent, {
{ 0., st::premiumButtonBg1->c },
{ 0.6, st::premiumButtonBg2->c },
{ 1., st::premiumButtonBg3->c },
});
}
[[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);
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 = st::boxWideWidth;
box->setWidth(size);
box->setNoContentMargin(true);
box->addRow(StickerPreview(box, 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 - 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 - buttonPadding.left() - buttonPadding.right();
auto button = CreateUnlockButton(box, width);
button->setClickedCallback([=] {
controller->showSettings();
});
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(fill, *color)
: request.background.waitingForNegativePattern()
? SolidColorImage(fill, 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);
}
});
});
}

View File

@ -0,0 +1,18 @@
/*
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
*/
#pragma once
class DocumentData;
namespace Window {
class SessionController;
} // namespace Window
void ShowStickerPreviewBox(
not_null<Window::SessionController*> controller,
not_null<DocumentData*> document);

View File

@ -278,3 +278,23 @@ manageEmojiStatusTop: 25px;
inlineRadialSize: 44px; inlineRadialSize: 44px;
inlineFileSize: 44px; inlineFileSize: 44px;
premiumPreviewBox: Box(defaultBox) {
buttonPadding: margins(18px, 18px, 18px, 18px);
buttonHeight: 44px;
button: RoundButton(defaultActiveButton) {
height: 44px;
textTop: 12px;
font: font(13px semibold);
}
}
premiumPreviewAbout: FlatLabel(defaultFlatLabel) {
minWidth: 240px;
textFg: membersAboutLimitFg;
align: align(top);
}
premiumPreviewAboutPadding: margins(18px, 23px, 18px, 8px);
premiumPreviewButtonLabel: FlatLabel(defaultFlatLabel) {
textFg: premiumButtonFg;
style: semiboldTextStyle;
}

View File

@ -154,7 +154,8 @@ std::vector<TextPart> ParseText(
[](const MTPDmessageEntityBlockquote&) { [](const MTPDmessageEntityBlockquote&) {
return Type::Blockquote; }, return Type::Blockquote; },
[](const MTPDmessageEntityBankCard&) { return Type::BankCard; }, [](const MTPDmessageEntityBankCard&) { return Type::BankCard; },
[](const MTPDmessageEntitySpoiler&) { return Type::Spoiler; }); [](const MTPDmessageEntitySpoiler&) { return Type::Spoiler; },
[](const MTPDmessageEntityAnimatedEmoji&) { return Type::Unknown; });
part.text = mid(start, length); part.text = mid(start, length);
part.additional = entity.match( part.additional = entity.match(
[](const MTPDmessageEntityPre &data) { [](const MTPDmessageEntityPre &data) {

View File

@ -1606,6 +1606,10 @@ void HistoryWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
) | rpl::start_with_next(update, _chooseTheme->lifetime()); ) | rpl::start_with_next(update, _chooseTheme->lifetime());
} }
Ui::ChatTheme *HistoryWidget::customChatTheme() const {
return _list ? _list->theme().get() : nullptr;
}
void HistoryWidget::fieldChanged() { void HistoryWidget::fieldChanged() {
const auto updateTyping = (_textUpdateEvents & TextUpdateEvent::SendTyping); const auto updateTyping = (_textUpdateEvents & TextUpdateEvent::SendTyping);
@ -7821,7 +7825,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
Window::SectionWidget::PaintBackground( Window::SectionWidget::PaintBackground(
controller(), controller(),
_list ? _list->theme().get() : controller()->defaultChatTheme().get(), controller()->currentChatTheme(),
this, this,
e->rect()); e->rect());

View File

@ -242,6 +242,7 @@ public:
void saveFieldToHistoryLocalDraft(); void saveFieldToHistoryLocalDraft();
void toggleChooseChatTheme(not_null<PeerData*> peer); void toggleChooseChatTheme(not_null<PeerData*> peer);
[[nodiscard]] Ui::ChatTheme *customChatTheme() const;
void applyCloudDraft(History *history); void applyCloudDraft(History *history);

View File

@ -418,9 +418,9 @@ void Element::externalLottieProgressing(bool external) const {
} }
} }
bool Element::externalLottieTill(int frame) const { bool Element::externalLottieTill(ExternalLottieInfo info) const {
if (const auto media = _media.get()) { if (const auto media = _media.get()) {
return media->externalLottieTill(frame); return media->externalLottieTill(info);
} }
return true; return true;
} }

View File

@ -44,6 +44,7 @@ enum class InfoDisplayType : char;
struct StateRequest; struct StateRequest;
struct TextState; struct TextState;
class Media; class Media;
struct ExternalLottieInfo;
using PaintContext = Ui::ChatPaintContext; using PaintContext = Ui::ChatPaintContext;
@ -272,7 +273,7 @@ public:
void refreshDataId(); void refreshDataId();
void externalLottieProgressing(bool external) const; void externalLottieProgressing(bool external) const;
bool externalLottieTill(int frame) const; bool externalLottieTill(ExternalLottieInfo info) const;
QDateTime dateTime() const; QDateTime dateTime() const;

View File

@ -25,7 +25,8 @@ namespace HistoryView {
namespace { namespace {
constexpr auto kEmojiMultiplier = 3; constexpr auto kEmojiMultiplier = 3;
constexpr auto kPremiumMultiplier = 2.25; constexpr auto kPremiumShift = 0.082;
constexpr auto kPremiumMultiplier = 1.5;
constexpr auto kEmojiCachesCount = 4; constexpr auto kEmojiCachesCount = 4;
constexpr auto kPremiumCachesCount = 8; constexpr auto kPremiumCachesCount = 8;
constexpr auto kMaxPlays = 5; constexpr auto kMaxPlays = 5;
@ -277,7 +278,9 @@ QRect EmojiInteractions::computeRect(
const auto fullWidth = view->width(); const auto fullWidth = view->width();
const auto sticker = premium ? _premiumSize : _emojiSize; const auto sticker = premium ? _premiumSize : _emojiSize;
const auto size = sizeFor(premium); const auto size = sizeFor(premium);
const auto shift = size.width() / 40; const auto shift = premium
? int(_premiumSize.width() * kPremiumShift)
: (size.width() / 40);
const auto inner = view->innerGeometry(); const auto inner = view->innerGeometry();
const auto rightAligned = view->hasOutLayout() const auto rightAligned = view->hasOutLayout()
&& !view->delegate()->elementIsChatWide(); && !view->delegate()->elementIsChatWide();
@ -325,7 +328,11 @@ void EmojiInteractions::paint(QPainter &p) {
p.drawImage( p.drawImage(
QRect(rect.topLeft(), frame.image.size() / factor), QRect(rect.topLeft(), frame.image.size() / factor),
frame.image); frame.image);
if (!play.premium || play.view->externalLottieTill(frame.index)) { const auto info = HistoryView::ExternalLottieInfo{
.frame = frame.index,
.count = play.framesCount,
};
if (!play.premium || play.view->externalLottieTill(info)) {
play.lottie->markFrameShown(); play.lottie->markFrameShown();
} }
} }

View File

@ -69,6 +69,11 @@ enum class MediaInBubbleState {
TimeId duration, TimeId duration,
const QString &base); const QString &base);
struct ExternalLottieInfo {
int frame = -1;
int count = -1;
};
class Media : public Object { class Media : public Object {
public: public:
explicit Media(not_null<Element*> parent) : _parent(parent) { explicit Media(not_null<Element*> parent) : _parent(parent) {
@ -175,11 +180,11 @@ public:
virtual void externalLottieProgressing(bool external) { virtual void externalLottieProgressing(bool external) {
} }
virtual bool externalLottieTill(int frame) { virtual bool externalLottieTill(ExternalLottieInfo info) {
return true; return true;
} }
virtual int externalLottieTillFrame() const { virtual ExternalLottieInfo externalLottieInfo() const {
return -1; return {};
} }
[[nodiscard]] virtual QSize sizeForGroupingOptimal(int maxWidth) const { [[nodiscard]] virtual QSize sizeForGroupingOptimal(int maxWidth) const {

View File

@ -464,12 +464,12 @@ void UnwrappedMedia::externalLottieProgressing(bool external) {
_content->externalLottieProgressing(external); _content->externalLottieProgressing(external);
} }
bool UnwrappedMedia::externalLottieTill(int frame) { bool UnwrappedMedia::externalLottieTill(ExternalLottieInfo info) {
return _content->externalLottieTill(frame); return _content->externalLottieTill(info);
} }
int UnwrappedMedia::externalLottieTillFrame() const { ExternalLottieInfo UnwrappedMedia::externalLottieInfo() const {
return _content->externalLottieTillFrame(); return _content->externalLottieInfo();
} }
int UnwrappedMedia::calculateFullRight(const QRect &inner) const { int UnwrappedMedia::calculateFullRight(const QRect &inner) const {

View File

@ -43,11 +43,11 @@ public:
virtual void externalLottieProgressing(bool external) { virtual void externalLottieProgressing(bool external) {
} }
virtual bool externalLottieTill(int frame) { virtual bool externalLottieTill(ExternalLottieInfo info) {
return true; return true;
} }
virtual int externalLottieTillFrame() const { virtual ExternalLottieInfo externalLottieInfo() const {
return -1; return {};
} }
virtual bool hasHeavyPart() const { virtual bool hasHeavyPart() const {
@ -103,8 +103,8 @@ public:
const Lottie::ColorReplacements *replacements) override; const Lottie::ColorReplacements *replacements) override;
void externalLottieProgressing(bool external) override; void externalLottieProgressing(bool external) override;
bool externalLottieTill(int frame) override; bool externalLottieTill(ExternalLottieInfo info) override;
int externalLottieTillFrame() const override; ExternalLottieInfo externalLottieInfo() const override;
bool hasHeavyPart() const override { bool hasHeavyPart() const override {
return _content->hasHeavyPart(); return _content->hasHeavyPart();

View File

@ -75,7 +75,7 @@ Sticker::Sticker(
if (const auto media = replacing ? replacing->media() : nullptr) { if (const auto media = replacing ? replacing->media() : nullptr) {
_lottie = media->stickerTakeLottie(_data, _replacements); _lottie = media->stickerTakeLottie(_data, _replacements);
if (_lottie) { if (_lottie) {
_externalTillFrame = media->externalLottieTillFrame(); _externalInfo = media->externalLottieInfo();
if (_data->isPremiumSticker() if (_data->isPremiumSticker()
&& !_premiumEffectPlayed) { && !_premiumEffectPlayed) {
_premiumEffectPlayed = true; _premiumEffectPlayed = true;
@ -217,8 +217,8 @@ void Sticker::paintLottie(
const auto count = _lottie->information().framesCount; const auto count = _lottie->information().framesCount;
_frameIndex = frame.index; _frameIndex = frame.index;
_framesCount = count; _framesCount = count;
const auto paused = (_externalTillFrame >= 0) const auto paused = (_externalInfo.frame >= 0)
? (_frameIndex >= _externalTillFrame) ? (_frameIndex % _externalInfo.count >= _externalInfo.frame)
: _parent->delegate()->elementIsGifPaused(); : _parent->delegate()->elementIsGifPaused();
_nextLastDiceFrame = !paused _nextLastDiceFrame = !paused
&& (_diceIndex > 0) && (_diceIndex > 0)
@ -230,7 +230,7 @@ void Sticker::paintLottie(
: (isEmojiSticker() : (isEmojiSticker()
|| !Core::App().settings().loopAnimatedStickers()); || !Core::App().settings().loopAnimatedStickers());
const auto lastDiceFrame = (_diceIndex > 0) && atTheEnd(); const auto lastDiceFrame = (_diceIndex > 0) && atTheEnd();
const auto switchToNext = (_externalTillFrame >= 0) const auto switchToNext = (_externalInfo.frame >= 0)
|| !playOnce || !playOnce
|| (!lastDiceFrame && (_frameIndex != 0 || !_lottieOncePlayed)); || (!lastDiceFrame && (_frameIndex != 0 || !_lottieOncePlayed));
if (!paused if (!paused
@ -407,7 +407,7 @@ void Sticker::setupLottie() {
_dataMedia.get(), _dataMedia.get(),
_replacements, _replacements,
ChatHelpers::StickerLottieSize::MessageHistory, ChatHelpers::StickerLottieSize::MessageHistory,
size() * cIntRetinaFactor(), size() * style::DevicePixelRatio(),
Lottie::Quality::High); Lottie::Quality::High);
if (_data->isPremiumSticker() if (_data->isPremiumSticker()
&& !_premiumEffectPlayed) { && !_premiumEffectPlayed) {
@ -466,29 +466,32 @@ std::unique_ptr<Lottie::SinglePlayer> Sticker::stickerTakeLottie(
} }
void Sticker::externalLottieProgressing(bool external) { void Sticker::externalLottieProgressing(bool external) {
_externalTillFrame = !external _externalInfo = !external
? -1 ? ExternalLottieInfo{}
: (_externalTillFrame > 0) : (_externalInfo.frame > 0)
? _externalTillFrame ? _externalInfo
: 0; : ExternalLottieInfo{ 0, 2 };
} }
bool Sticker::externalLottieTill(int frame) { bool Sticker::externalLottieTill(ExternalLottieInfo info) {
_externalTillFrame = (_externalTillFrame >= 0) ? frame : -1; if (_externalInfo.frame >= 0) {
_externalInfo = info;
}
return markFramesTillExternal(); return markFramesTillExternal();
} }
int Sticker::externalLottieTillFrame() const { ExternalLottieInfo Sticker::externalLottieInfo() const {
return _externalTillFrame; return _externalInfo;
} }
bool Sticker::markFramesTillExternal() { bool Sticker::markFramesTillExternal() {
if (_externalTillFrame < 0 || !_lottie) { if (_externalInfo.frame < 0 || !_lottie) {
return true; return true;
} else if (!_lottie->ready()) { } else if (!_lottie->ready()) {
return false; return false;
} }
while (_lottie->frameIndex() < _externalTillFrame) { const auto till = _externalInfo.frame % _lottie->framesCount();
while (_lottie->frameIndex() < till) {
if (!_lottie->markFrameShown()) { if (!_lottie->markFrameShown()) {
return false; return false;
} }

View File

@ -52,8 +52,8 @@ public:
const Lottie::ColorReplacements *replacements) override; const Lottie::ColorReplacements *replacements) override;
void externalLottieProgressing(bool external) override; void externalLottieProgressing(bool external) override;
bool externalLottieTill(int frame) override; bool externalLottieTill(ExternalLottieInfo info) override;
int externalLottieTillFrame() const override; ExternalLottieInfo externalLottieInfo() const override;
bool hasHeavyPart() const override; bool hasHeavyPart() const override;
void unloadHeavyPart() override; void unloadHeavyPart() override;
@ -107,10 +107,10 @@ private:
QSize _size; QSize _size;
QImage _lastDiceFrame; QImage _lastDiceFrame;
QString _diceEmoji; QString _diceEmoji;
ExternalLottieInfo _externalInfo;
int _diceIndex = -1; int _diceIndex = -1;
mutable int _frameIndex = -1; mutable int _frameIndex = -1;
mutable int _framesCount = -1; mutable int _framesCount = -1;
int _externalTillFrame = -1;
mutable bool _lottieOncePlayed = false; mutable bool _lottieOncePlayed = false;
mutable bool _premiumEffectPlayed = false; mutable bool _premiumEffectPlayed = false;
mutable bool _nextLastDiceFrame = false; mutable bool _nextLastDiceFrame = false;

View File

@ -1476,10 +1476,14 @@ void MainWidget::ui_showPeerHistory(
floatPlayerCheckVisibility(); floatPlayerCheckVisibility();
} }
PeerData *MainWidget::peer() { PeerData *MainWidget::peer() const {
return _history->peer(); return _history->peer();
} }
Ui::ChatTheme *MainWidget::customChatTheme() const {
return _history->customChatTheme();
}
void MainWidget::saveSectionInStack() { void MainWidget::saveSectionInStack() {
if (_mainSection) { if (_mainSection) {
if (auto memento = _mainSection->createMemento()) { if (auto memento = _mainSection->createMemento()) {

View File

@ -70,6 +70,7 @@ struct Content;
} // namespace Export } // namespace Export
namespace Ui { namespace Ui {
class ChatTheme;
class ConfirmBox; class ConfirmBox;
class ResizeArea; class ResizeArea;
class PlainShadow; class PlainShadow;
@ -144,7 +145,8 @@ public:
void dialogsToUp(); void dialogsToUp();
void checkHistoryActivation(); void checkHistoryActivation();
PeerData *peer(); [[nodiscard]] PeerData *peer() const;
[[nodiscard]] Ui::ChatTheme *customChatTheme() const;
int backgroundFromY() const; int backgroundFromY() const;
void showSection( void showSection(

View File

@ -51,7 +51,7 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5;
return (doubled % 2) ? 0.5 : 1.; return (doubled % 2) ? 0.5 : 1.;
} }
[[nodiscard]] CacheBackgroundResult CacheBackground( [[nodiscard]] CacheBackgroundResult CacheBackgroundByRequest(
const CacheBackgroundRequest &request) { const CacheBackgroundRequest &request) {
Expects(!request.area.isEmpty()); Expects(!request.area.isEmpty());
@ -205,6 +205,11 @@ bool operator!=(
return !(a == b); return !(a == b);
} }
CacheBackgroundResult CacheBackground(
const CacheBackgroundRequest &request) {
return CacheBackgroundByRequest(request);
}
CachedBackground::CachedBackground(CacheBackgroundResult &&result) CachedBackground::CachedBackground(CacheBackgroundResult &&result)
: pixmap(PixmapFromImage(std::move(result.image))) : pixmap(PixmapFromImage(std::move(result.image)))
, area(result.area) , area(result.area)

View File

@ -86,6 +86,9 @@ struct CacheBackgroundResult {
bool waitingForNegativePattern = false; bool waitingForNegativePattern = false;
}; };
[[nodiscard]] CacheBackgroundResult CacheBackground(
const CacheBackgroundRequest &request);
struct CachedBackground { struct CachedBackground {
CachedBackground() = default; CachedBackground() = default;
CachedBackground(CacheBackgroundResult &&result); CachedBackground(CacheBackgroundResult &&result);
@ -174,6 +177,10 @@ public:
[[nodiscard]] rpl::producer<> repaintBackgroundRequests() const; [[nodiscard]] rpl::producer<> repaintBackgroundRequests() const;
void rotateComplexGradientBackground(); void rotateComplexGradientBackground();
[[nodiscard]] CacheBackgroundRequest cacheBackgroundRequest(
QSize area,
int addRotation = 0) const;
private: private:
void cacheBackground(); void cacheBackground();
void cacheBackgroundNow(); void cacheBackgroundNow();
@ -181,9 +188,6 @@ private:
const CacheBackgroundRequest &request, const CacheBackgroundRequest &request,
Fn<void(CacheBackgroundResult&&)> done = nullptr); Fn<void(CacheBackgroundResult&&)> done = nullptr);
void setCachedBackground(CacheBackgroundResult &&cached); void setCachedBackground(CacheBackgroundResult &&cached);
[[nodiscard]] CacheBackgroundRequest cacheBackgroundRequest(
QSize area,
int addRotation = 0) const;
[[nodiscard]] bool readyForBackgroundRotation() const; [[nodiscard]] bool readyForBackgroundRotation() const;
void generateNextBackgroundRotation(); void generateNextBackgroundRotation();

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/toasts/common_toasts.h" #include "ui/toasts/common_toasts.h"
#include "boxes/sticker_preview_box.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_document.h" #include "data/data_document.h"
@ -334,9 +335,7 @@ bool ShowSendPremiumError(
|| document->session().user()->isPremium()) { || document->session().user()->isPremium()) {
return false; return false;
} }
Ui::ShowMultilineToast({ ShowStickerPreviewBox(controller, document);
.text = { u"Premium sticker."_q },
});
return true; return true;
} }

View File

@ -26,8 +26,9 @@ namespace Window {
namespace { namespace {
constexpr auto kStickerPreviewEmojiLimit = 10; constexpr auto kStickerPreviewEmojiLimit = 10;
constexpr auto kPremiumMultiplier = 2.25; constexpr auto kPremiumShift = 0.082;
constexpr auto kPremiumDownscale = 1.5; constexpr auto kPremiumMultiplier = 1.5;
constexpr auto kPremiumDownscale = 1.25;
} // namespace } // namespace
@ -71,6 +72,10 @@ void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
: Lottie::Animation::FrameInfo(); : Lottie::Animation::FrameInfo();
const auto image = frame.image; const auto image = frame.image;
const auto effectImage = effect.image; const auto effectImage = effect.image;
const auto framesCount = !image.isNull() ? _lottie->framesCount() : 1;
const auto effectsCount = !effectImage.isNull()
? _effect->framesCount()
: 1;
const auto pixmap = image.isNull() ? currentImage() : QPixmap(); const auto pixmap = image.isNull() ? currentImage() : QPixmap();
const auto size = image.isNull() ? pixmap.size() : image.size(); const auto size = image.isNull() ? pixmap.size() : image.size();
int w = size.width() / factor, h = size.height() / factor; int w = size.width() / factor, h = size.height() / factor;
@ -111,10 +116,12 @@ void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
emojiLeft += _emojiSize + st::stickerEmojiSkip; emojiLeft += _emojiSize + st::stickerEmojiSkip;
} }
} }
if (!frame.image.isNull() && frame.index <= effect.index) { if (!frame.image.isNull()
&& (!_effect || ((frame.index % effectsCount) <= effect.index))) {
_lottie->markFrameShown(); _lottie->markFrameShown();
} }
if (!effect.image.isNull() && effect.index <= frame.index) { if (!effect.image.isNull()
&& ((effect.index % framesCount) <= frame.index)) {
_effect->markFrameShown(); _effect->markFrameShown();
} }
} }
@ -130,7 +137,7 @@ QPoint MediaPreviewWidget::innerPosition(QSize size) const {
(height() - size.height()) / 2); (height() - size.height()) / 2);
} }
const auto outer = size * kPremiumMultiplier; const auto outer = size * kPremiumMultiplier;
const auto shift = outer.width() / 40; const auto shift = size.width() * kPremiumShift;
return outerPosition(size) return outerPosition(size)
+ QPoint( + QPoint(
outer.width() - size.width() - shift, outer.width() - size.width() - shift,

View File

@ -1654,6 +1654,13 @@ void SessionController::pushLastUsedChatTheme(
} }
} }
not_null<Ui::ChatTheme*> SessionController::currentChatTheme() const {
if (const auto custom = content()->customChatTheme()) {
return custom;
}
return defaultChatTheme().get();
}
void SessionController::setChatStyleTheme( void SessionController::setChatStyleTheme(
const std::shared_ptr<Ui::ChatTheme> &theme) { const std::shared_ptr<Ui::ChatTheme> &theme) {
if (_chatStyleTheme.lock() == theme) { if (_chatStyleTheme.lock() == theme) {

View File

@ -473,6 +473,7 @@ public:
void setChatStyleTheme(const std::shared_ptr<Ui::ChatTheme> &theme); void setChatStyleTheme(const std::shared_ptr<Ui::ChatTheme> &theme);
void clearCachedChatThemes(); void clearCachedChatThemes();
void pushLastUsedChatTheme(const std::shared_ptr<Ui::ChatTheme> &theme); void pushLastUsedChatTheme(const std::shared_ptr<Ui::ChatTheme> &theme);
[[nodiscard]] not_null<Ui::ChatTheme*> currentChatTheme() const;
void overridePeerTheme( void overridePeerTheme(
not_null<PeerData*> peer, not_null<PeerData*> peer,

@ -1 +1 @@
Subproject commit fb3bb0ef3d46e61debbe3b3dd996bd5050275ba3 Subproject commit b2fd42d374051d2ba8ff2eb180814f5ee2f99c8a