diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index cc279de095..258a5463e7 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f8611e0a3e..1c1b97f8b2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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"; diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 88836a89cb..5b49ebf874 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -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; diff --git a/Telegram/SourceFiles/boxes/sticker_preview_box.cpp b/Telegram/SourceFiles/boxes/sticker_preview_box.cpp new file mode 100644 index 0000000000..052da279cd --- /dev/null +++ b/Telegram/SourceFiles/boxes/sticker_preview_box.cpp @@ -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 document; + std::shared_ptr media; + base::weak_ptr controller; +}; + +[[nodiscard]] std::vector &Preloads() { + static auto result = std::vector(); + return result; +} + +void PreloadSticker(const std::shared_ptr &media) { + const auto origin = media->owner()->stickerSetOrigin(); + media->automaticLoad(origin, nullptr); + media->videoThumbnailWanted(origin); +} + +[[nodiscard]] object_ptr StickerPreview( + QWidget *parent, + const std::shared_ptr &media, + const QImage &back, + int size) { + auto result = object_ptr(parent, size); + const auto raw = result.data(); + auto &lifetime = raw->lifetime(); + + struct State { + std::unique_ptr lottie; + std::unique_ptr effect; + }; + const auto state = lifetime.make_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::ReadContent(media->bytes(), document->filepath()), + Lottie::FrameRequest{ lottieSize * factor }, + Lottie::Quality::High); + state->effect = std::make_unique( + 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 CreateGradientButton( + QWidget *parent, + QGradientStops stops) { + return object_ptr(parent, std::move(stops)); +} + +[[nodiscard]] object_ptr CreatePremiumButton( + QWidget *parent) { + return CreateGradientButton(parent, { + { 0., st::premiumButtonBg1->c }, + { 0.6, st::premiumButtonBg2->c }, + { 1., st::premiumButtonBg3->c }, + }); +} + +[[nodiscard]] object_ptr 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( + 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 box, + not_null controller, + const std::shared_ptr &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( + box, + tr::lng_sticker_premium_about(), + st::premiumPreviewAbout); + label->resizeToWidth(size - padding.left() - padding.right()); + box->addRow( + object_ptr>( + 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 controller, + const std::shared_ptr &media, + QImage back) { + controller->show(Box(StickerBox, controller, media, back)); +} + +void Show(not_null 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 controller, + not_null 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); + } + }); + }); +} diff --git a/Telegram/SourceFiles/boxes/sticker_preview_box.h b/Telegram/SourceFiles/boxes/sticker_preview_box.h new file mode 100644 index 0000000000..3e206c7bc1 --- /dev/null +++ b/Telegram/SourceFiles/boxes/sticker_preview_box.h @@ -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 controller, + not_null document); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 90d3914763..d5c19ccbae 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -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; +} diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 67d6d7772a..db81318cc4 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -154,7 +154,8 @@ std::vector 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) { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 0d338584eb..a8b2d9b296 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1606,6 +1606,10 @@ void HistoryWidget::toggleChooseChatTheme(not_null 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()); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index db1085ff09..67d5a86147 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -242,6 +242,7 @@ public: void saveFieldToHistoryLocalDraft(); void toggleChooseChatTheme(not_null peer); + [[nodiscard]] Ui::ChatTheme *customChatTheme() const; void applyCloudDraft(History *history); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index f191cba36d..9a0b37ecba 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -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; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 3571b405b9..fd6e4e4046 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -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; diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp index be6adfd074..4f2f8c9abc 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -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(); } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index a5716ed9ff..51c9f3944c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -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 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 { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 80a8f0d180..f15fa9ab53 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -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 { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h index e01bb9f192..0dff76b926 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -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(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index a6c4c45b91..cf110ec101 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -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 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; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.h b/Telegram/SourceFiles/history/view/media/history_view_sticker.h index 908357d7d3..408e6c049f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.h +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.h @@ -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; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 2ddacdf20e..80f0f32c5e 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -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()) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 9c2e82bb70..c7e3b2bb9c 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -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( diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.cpp b/Telegram/SourceFiles/ui/chat/chat_theme.cpp index b07c0c23d8..a01be5a7d8 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_theme.cpp @@ -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) diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.h b/Telegram/SourceFiles/ui/chat/chat_theme.h index 31f766e2f8..65f602a01a 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.h +++ b/Telegram/SourceFiles/ui/chat/chat_theme.h @@ -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 done = nullptr); void setCachedBackground(CacheBackgroundResult &&cached); - [[nodiscard]] CacheBackgroundRequest cacheBackgroundRequest( - QSize area, - int addRotation = 0) const; [[nodiscard]] bool readyForBackgroundRotation() const; void generateNextBackgroundRotation(); diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index e8d40e4e0e..76c766d5ba 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -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; } diff --git a/Telegram/SourceFiles/window/window_media_preview.cpp b/Telegram/SourceFiles/window/window_media_preview.cpp index 594a36d2a5..00c0473670 100644 --- a/Telegram/SourceFiles/window/window_media_preview.cpp +++ b/Telegram/SourceFiles/window/window_media_preview.cpp @@ -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, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index bc3a659fe0..9c0e32d663 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1654,6 +1654,13 @@ void SessionController::pushLastUsedChatTheme( } } +not_null SessionController::currentChatTheme() const { + if (const auto custom = content()->customChatTheme()) { + return custom; + } + return defaultChatTheme().get(); +} + void SessionController::setChatStyleTheme( const std::shared_ptr &theme) { if (_chatStyleTheme.lock() == theme) { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index f038d14ff5..c28f374d30 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -473,6 +473,7 @@ public: void setChatStyleTheme(const std::shared_ptr &theme); void clearCachedChatThemes(); void pushLastUsedChatTheme(const std::shared_ptr &theme); + [[nodiscard]] not_null currentChatTheme() const; void overridePeerTheme( not_null peer, diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie index fb3bb0ef3d..b2fd42d374 160000 --- a/Telegram/lib_lottie +++ b/Telegram/lib_lottie @@ -1 +1 @@ -Subproject commit fb3bb0ef3d46e61debbe3b3dd996bd5050275ba3 +Subproject commit b2fd42d374051d2ba8ff2eb180814f5ee2f99c8a