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/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

View File

@ -224,6 +224,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_gif_error" = "An error has occurred while reading GIF animation :(";
"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;
messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity;
messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity;
messageEntityAnimatedEmoji#5eef0214 offset:int length:int = MessageEntity;
inputChannelEmpty#ee8c1e86 = 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;
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&) {
return Type::Blockquote; },
[](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.additional = entity.match(
[](const MTPDmessageEntityPre &data) {

View File

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

View File

@ -242,6 +242,7 @@ public:
void saveFieldToHistoryLocalDraft();
void toggleChooseChatTheme(not_null<PeerData*> peer);
[[nodiscard]] Ui::ChatTheme *customChatTheme() const;
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()) {
return media->externalLottieTill(frame);
return media->externalLottieTill(info);
}
return true;
}

View File

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

View File

@ -25,7 +25,8 @@ namespace HistoryView {
namespace {
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 kPremiumCachesCount = 8;
constexpr auto kMaxPlays = 5;
@ -277,7 +278,9 @@ QRect EmojiInteractions::computeRect(
const auto fullWidth = view->width();
const auto sticker = premium ? _premiumSize : _emojiSize;
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 rightAligned = view->hasOutLayout()
&& !view->delegate()->elementIsChatWide();
@ -325,7 +328,11 @@ void EmojiInteractions::paint(QPainter &p) {
p.drawImage(
QRect(rect.topLeft(), frame.image.size() / factor),
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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +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 "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_document.h"
@ -334,9 +335,7 @@ bool ShowSendPremiumError(
|| document->session().user()->isPremium()) {
return false;
}
Ui::ShowMultilineToast({
.text = { u"Premium sticker."_q },
});
ShowStickerPreviewBox(controller, document);
return true;
}

View File

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

View File

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

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