diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e0acb5c18..06d89a81a 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -270,6 +270,8 @@ PRIVATE chat_helpers/stickers.h chat_helpers/stickers_emoji_pack.cpp chat_helpers/stickers_emoji_pack.h + chat_helpers/stickers_dice_pack.cpp + chat_helpers/stickers_dice_pack.h chat_helpers/stickers_list_widget.cpp chat_helpers/stickers_list_widget.h chat_helpers/tabbed_panel.cpp @@ -436,6 +438,8 @@ PRIVATE history/view/media/history_view_call.cpp history/view/media/history_view_contact.h history/view/media/history_view_contact.cpp + history/view/media/history_view_dice.h + history/view/media/history_view_dice.cpp history/view/media/history_view_document.h history/view/media/history_view_document.cpp history/view/media/history_view_file.h diff --git a/Telegram/Resources/art/dice_idle.tgs b/Telegram/Resources/art/dice_idle.tgs new file mode 100644 index 000000000..aad33bffe Binary files /dev/null and b/Telegram/Resources/art/dice_idle.tgs differ diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index 2a9d64d3c..68b67373b 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -47,6 +47,7 @@ ../../art/logo_256.png ../../art/logo_256_no_margin.png ../../art/sunrise.jpg + ../../art/dice_idle.tgs ../../day-blue.tdesktop-theme ../../night.tdesktop-theme ../../night-green.tdesktop-theme diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp new file mode 100644 index 000000000..1e9c6a7f6 --- /dev/null +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -0,0 +1,96 @@ +/* +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 "chat_helpers/stickers_dice_pack.h" + +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/data_document.h" +#include "storage/localimageloader.h" +#include "base/unixtime.h" +#include "apiwrap.h" + +#include +#include + +namespace Stickers { +namespace { + +constexpr auto kZeroDiceDocumentId = 0xa3b83c9f84fa9e83ULL; + +} // namespace + +DicePack::DicePack(not_null session) +: _session(session) { +} + +DicePack::~DicePack() = default; + +DocumentData *DicePack::lookup(int value) { + if (!_requestId) { + load(); + } + if (!value) { + ensureZeroGenerated(); + return _zero; + } + const auto i = _map.find(value); + return (i != end(_map)) ? i->second.get() : nullptr; +} + +void DicePack::load() { + if (_requestId) { + return; + } + _requestId = _session->api().request(MTPmessages_GetStickerSet( + MTP_inputStickerSetDice() + )).done([=](const MTPmessages_StickerSet &result) { + result.match([&](const MTPDmessages_stickerSet &data) { + applySet(data); + }); + }).fail([=](const RPCError &error) { + _requestId = 0; + }).send(); +} + +void DicePack::applySet(const MTPDmessages_stickerSet &data) { + auto index = 0; + for (const auto &sticker : data.vdocuments().v) { + const auto document = _session->data().processDocument( + sticker); + if (document->sticker()) { + _map.emplace(++index, document); + } + } +} + +void DicePack::ensureZeroGenerated() { + if (_zero) { + return; + } + + const auto path = qsl(":/gui/art/dice_idle.tgs"); + auto task = FileLoadTask( + path, + QByteArray(), + nullptr, + SendMediaType::File, + FileLoadTo(0, {}, 0), + {}); + task.process(); + const auto result = task.peekResult(); + Assert(result != nullptr); + _zero = _session->data().processDocument( + result->document, + std::move(result->thumb)); + _zero->setLocation(FileLocation(path)); + + Ensures(_zero->sticker()); + Ensures(_zero->sticker()->animated); +} + +} // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h new file mode 100644 index 000000000..59a22a11a --- /dev/null +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h @@ -0,0 +1,37 @@ +/* +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 Main { +class Session; +} // namespace Main + +namespace Stickers { + +class DicePack final { +public: + explicit DicePack(not_null session); + ~DicePack(); + + DocumentData *lookup(int value); + +private: + void load(); + void applySet(const MTPDmessages_stickerSet &data); + void ensureZeroGenerated(); + + not_null _session; + base::flat_map> _map; + DocumentData *_zero = nullptr; + mtpRequestId _requestId = 0; + +}; + +} // namespace Stickers diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index e0f6bd6f7..5e6a9d9c0 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_web_page.h" #include "history/view/media/history_view_poll.h" #include "history/view/media/history_view_theme_document.h" +#include "history/view/media/history_view_dice.h" #include "ui/image/image.h" #include "ui/image/image_source.h" #include "ui/text_options.h" @@ -1325,4 +1326,49 @@ std::unique_ptr MediaPoll::createView( return std::make_unique(message, _poll); } +MediaDice::MediaDice(not_null parent, int value) +: Media(parent) +, _value(value) { +} + +std::unique_ptr MediaDice::clone(not_null parent) { + return std::make_unique(parent, _value); +} + +int MediaDice::diceValue() const { + return _value; +} + +QString MediaDice::notificationText() const { + return QString::fromUtf8("\xF0\x9F\x8E\xB2"); +} + +QString MediaDice::pinnedTextSubstring() const { + return QChar(171) + notificationText() + QChar(187); +} + +TextForMimeData MediaDice::clipboardText() const { + return { notificationText() }; +} + +bool MediaDice::updateInlineResultMedia(const MTPMessageMedia &media) { + return updateSentMedia(media); +} + +bool MediaDice::updateSentMedia(const MTPMessageMedia &media) { + if (media.type() != mtpc_messageMediaDice) { + return false; + } + _value = media.c_messageMediaDice().vvalue().v; + return true; +} + +std::unique_ptr MediaDice::createView( + not_null message, + not_null realParent) { + return std::make_unique( + message, + std::make_unique(message, _value)); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 7803e503d..6b77e7255 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -118,7 +118,7 @@ private: }; -class MediaPhoto : public Media { +class MediaPhoto final : public Media { public: MediaPhoto( not_null parent, @@ -158,7 +158,7 @@ private: }; -class MediaFile : public Media { +class MediaFile final : public Media { public: MediaFile( not_null parent, @@ -195,7 +195,7 @@ private: }; -class MediaContact : public Media { +class MediaContact final : public Media { public: MediaContact( not_null parent, @@ -223,7 +223,7 @@ private: }; -class MediaLocation : public Media { +class MediaLocation final : public Media { public: MediaLocation( not_null parent, @@ -255,7 +255,7 @@ private: }; -class MediaCall : public Media { +class MediaCall final : public Media { public: MediaCall( not_null parent, @@ -284,7 +284,7 @@ private: }; -class MediaWebPage : public Media { +class MediaWebPage final : public Media { public: MediaWebPage( not_null parent, @@ -316,7 +316,7 @@ private: }; -class MediaGame : public Media { +class MediaGame final : public Media { public: MediaGame( not_null parent, @@ -348,7 +348,7 @@ private: }; -class MediaInvoice : public Media { +class MediaInvoice final : public Media { public: MediaInvoice( not_null parent, @@ -378,7 +378,7 @@ private: }; -class MediaPoll : public Media { +class MediaPoll final : public Media { public: MediaPoll( not_null parent, @@ -405,6 +405,28 @@ private: }; +class MediaDice final : public Media { +public: + MediaDice(not_null parent, int value); + + std::unique_ptr clone(not_null parent) override; + + int diceValue() const; + + QString notificationText() const override; + QString pinnedTextSubstring() const override; + TextForMimeData clipboardText() const override; + bool updateInlineResultMedia(const MTPMessageMedia &media) override; + bool updateSentMedia(const MTPMessageMedia &media) override; + std::unique_ptr createView( + not_null message, + not_null realParent) override; + +private: + int _value = 0; + +}; + TextForMimeData WithCaptionClipboardText( const QString &attachType, TextForMimeData &&caption); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 0503a60d5..4bc32948a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -154,7 +154,7 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { }, [](const MTPDmessageMediaPoll &) { return Result::Good; }, [](const MTPDmessageMediaDice &) { - return Result::Unsupported; // #TODO dice + return Result::Good; }, [](const MTPDmessageMediaUnsupported &) { return Result::Unsupported; }); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index d2b0fdfe1..c461982b4 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1024,8 +1024,8 @@ std::unique_ptr HistoryMessage::CreateMedia( return std::make_unique( item, item->history()->owner().processPoll(media)); - }, [](const MTPDmessageMediaDice &media) -> Result { - return nullptr; // #TODO dice + }, [&](const MTPDmessageMediaDice &media) -> Result { + return std::make_unique(item, media.vvalue().v); }, [](const MTPDmessageMediaEmpty &) -> Result { return nullptr; }, [](const MTPDmessageMediaUnsupported &) -> Result { diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp new file mode 100644 index 000000000..0f780802d --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -0,0 +1,60 @@ +/* +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 "history/view/media/history_view_dice.h" + +#include "data/data_session.h" +#include "chat_helpers/stickers_dice_pack.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "main/main_session.h" + +namespace HistoryView { +namespace { + +DocumentData *Lookup(not_null view, int value) { + const auto &session = view->data()->history()->session(); + return session.diceStickersPack().lookup(value); +} + +} // namespace + +Dice::Dice(not_null parent, int value) +: _parent(parent) +, _start(parent, Lookup(parent, 0)) +, _value(value) { + _start.setDiceIndex(0); +} + +Dice::~Dice() = default; + +QSize Dice::size() { + return _start.size(); +} + +void Dice::draw(Painter &p, const QRect &r, bool selected) { + Expects(_end.has_value() || !_drawingEnd); + + if (_drawingEnd) { + _end->draw(p, r, selected); + } else { + _start.draw(p, r, selected); + if (!_end && _value) { + if (const auto document = Lookup(_parent, _value)) { + _end.emplace(_parent, document); + _end->setDiceIndex(_value); + _end->initSize(); + } + } + if (_end && _end->readyToDrawLottie() && _start.atTheEnd()) { + _drawingEnd = true; + } + } +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.h b/Telegram/SourceFiles/history/view/media/history_view_dice.h new file mode 100644 index 000000000..5d0d7c4a9 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.h @@ -0,0 +1,43 @@ +/* +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 + +#include "history/view/media/history_view_media_unwrapped.h" +#include "history/view/media/history_view_sticker.h" + +namespace HistoryView { + +class Dice final : public UnwrappedMedia::Content { +public: + Dice(not_null parent, int value); + ~Dice(); + + QSize size() override; + void draw(Painter &p, const QRect &r, bool selected) override; + + void clearStickerLoopPlayed() override { + _lottieOncePlayed = false; + } + void unloadHeavyPart() override { + _start.unloadHeavyPart(); + if (_end) { + _end->unloadHeavyPart(); + } + } + +private: + const not_null _parent; + std::optional _end; + Sticker _start; + int _value = 0; + mutable bool _lottieOncePlayed = false; + mutable bool _drawingEnd = false; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index 717a0bf78..00e2b9bce 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -57,7 +57,7 @@ bool Sticker::isEmojiSticker() const { return (_parent->data()->media() == nullptr); } -QSize Sticker::size() { +void Sticker::initSize() { _size = _document->dimensions; if (isEmojiSticker()) { constexpr auto kIdealStickerSize = 512; @@ -70,14 +70,22 @@ QSize Sticker::size() { _size = DownscaledSize( _size, { st::maxStickerSize, st::maxStickerSize }); + [[maybe_unused]] bool result = readyToDrawLottie(); } +} + +QSize Sticker::size() { + initSize(); return _size; } -void Sticker::draw(Painter &p, const QRect &r, bool selected) { +bool Sticker::readyToDrawLottie() { + if (!_lastDiceFrame.isNull()) { + return true; + } const auto sticker = _document->sticker(); if (!sticker) { - return; + return false; } _document->checkStickerLarge(); @@ -85,10 +93,14 @@ void Sticker::draw(Painter &p, const QRect &r, bool selected) { if (sticker->animated && !_lottie && loaded) { setupLottie(); } + return (_lottie && _lottie->ready()); +} - if (_lottie && _lottie->ready()) { +void Sticker::draw(Painter &p, const QRect &r, bool selected) { + if (readyToDrawLottie()) { paintLottie(p, r, selected); - } else if (!sticker->animated || !_replacements) { + } else if (_document->sticker() + && (!_document->sticker()->animated || !_replacements)) { paintPixmap(p, r, selected); } } @@ -96,24 +108,51 @@ void Sticker::draw(Painter &p, const QRect &r, bool selected) { void Sticker::paintLottie(Painter &p, const QRect &r, bool selected) { auto request = Lottie::FrameRequest(); request.box = _size * cIntRetinaFactor(); - if (selected) { + if (selected && !_nextLastDiceFrame) { request.colored = st::msgStickerOverlay->c; } - const auto frame = _lottie->frameInfo(request); - const auto size = frame.image.size() / cIntRetinaFactor(); + const auto frame = _lottie + ? _lottie->frameInfo(request) + : Lottie::Animation::FrameInfo(); + if (_nextLastDiceFrame) { + _nextLastDiceFrame = false; + _lastDiceFrame = frame.image; + } + const auto &image = _lastDiceFrame.isNull() + ? frame.image + : _lastDiceFrame; + const auto prepared = (!_lastDiceFrame.isNull() && selected) + ? Images::prepareColored(st::msgStickerOverlay->c, image) + : image; + const auto size = prepared.size() / cIntRetinaFactor(); p.drawImage( QRect( QPoint( r.x() + (r.width() - size.width()) / 2, r.y() + (r.height() - size.height()) / 2), size), - frame.image); + prepared); + if (!_lastDiceFrame.isNull()) { + return; + } const auto paused = App::wnd()->sessionController()->isGifPausedAtLeastFor(Window::GifPauseReason::Any); - const auto playOnce = isEmojiSticker() - || !_document->session().settings().loopAnimatedStickers(); + const auto playOnce = (_diceIndex > 0) + ? true + : (_diceIndex == 0) + ? false + : (isEmojiSticker() + || !_document->session().settings().loopAnimatedStickers()); + const auto count = _lottie->information().framesCount; + _atTheEnd = (frame.index + 1 == count); + _nextLastDiceFrame = !paused + && (_diceIndex > 0) + && (frame.index + 2 == count); + const auto lastDiceFrame = (_diceIndex > 0) && _atTheEnd; + const auto switchToNext = !playOnce + || (!lastDiceFrame && (frame.index != 0 || !_lottieOncePlayed)); if (!paused - && (!playOnce || frame.index != 0 || !_lottieOncePlayed) + && switchToNext && _lottie->markFrameShown() && playOnce && !_lottieOncePlayed) { @@ -188,6 +227,10 @@ void Sticker::refreshLink() { } } +void Sticker::setDiceIndex(int index) { + _diceIndex = index; +} + void Sticker::setupLottie() { _lottie = Stickers::LottiePlayerFromDocument( _document, diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.h b/Telegram/SourceFiles/history/view/media/history_view_sticker.h index 6ee7370e4..6c9f4d0a6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.h +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.h @@ -31,6 +31,7 @@ public: const Lottie::ColorReplacements *replacements = nullptr); ~Sticker(); + void initSize(); QSize size() override; void draw(Painter &p, const QRect &r, bool selected) override; ClickHandlerPtr link() override { @@ -48,6 +49,12 @@ public: } void refreshLink() override; + void setDiceIndex(int index); + [[nodiscard]] bool atTheEnd() const { + return _atTheEnd; + } + [[nodiscard]] bool readyToDrawLottie(); + private: [[nodiscard]] bool isEmojiSticker() const; void paintLottie(Painter &p, const QRect &r, bool selected); @@ -63,7 +70,11 @@ private: std::unique_ptr _lottie; ClickHandlerPtr _link; QSize _size; + QImage _lastDiceFrame; + int _diceIndex = -1; mutable bool _lottieOncePlayed = false; + mutable bool _atTheEnd = false; + mutable bool _nextLastDiceFrame = false; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 90de3897d..303be0c59 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/changelogs.h" #include "main/main_account.h" #include "chat_helpers/stickers_emoji_pack.h" +#include "chat_helpers/stickers_dice_pack.h" #include "storage/file_download.h" #include "storage/download_manager_mtproto.h" #include "storage/file_upload.h" @@ -56,6 +57,7 @@ Session::Session( , _data(std::make_unique(this)) , _user(_data->processUser(user)) , _emojiStickersPack(std::make_unique(this)) +, _diceStickersPack(std::make_unique(this)) , _changelogs(Core::Changelogs::Create(this)) , _supportHelper(Support::Helper::Create(this)) { Core::App().passcodeLockChanges( diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 10ef88afb..552bd7630 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -46,6 +46,7 @@ class Instance; namespace Stickers { class EmojiPack; +class DicePack; } // namespace Stickers; namespace Core { @@ -89,9 +90,12 @@ public: [[nodiscard]] Storage::Facade &storage() { return *_storage; } - [[nodiscard]] Stickers::EmojiPack &emojiStickersPack() { + [[nodiscard]] Stickers::EmojiPack &emojiStickersPack() const { return *_emojiStickersPack; } + [[nodiscard]] Stickers::DicePack &diceStickersPack() const { + return *_diceStickersPack; + } [[nodiscard]] base::Observable &downloaderTaskFinished(); @@ -157,6 +161,7 @@ private: // _emojiStickersPack depends on _data. const std::unique_ptr _emojiStickersPack; + const std::unique_ptr _diceStickersPack; // _changelogs depends on _data, subscribes on chats loading event. const std::unique_ptr _changelogs; diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 53de99d95..5d497fe99 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -967,6 +967,10 @@ void FileLoadTask::finish() { } } +FileLoadResult *FileLoadTask::peekResult() const { + return _result.get(); +} + void FileLoadTask::removeFromAlbum() { if (!_album) { return; diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index 1f4ada86b..029b71637 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -292,6 +292,8 @@ public: void process(); void finish(); + FileLoadResult *peekResult() const; + private: static bool CheckForSong( const QString &filepath,