diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 169a9b915..e26b0497d 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -13,8 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_channel.h" #include "data/data_chat.h" +#include "data/data_document.h" +#include "data/data_document_media.h" #include "base/timer_rpl.h" #include "apiwrap.h" +#include "styles/style_chat.h" namespace Data { namespace { @@ -55,6 +58,105 @@ rpl::producer<> Reactions::updates() const { return _updated.events(); } +void Reactions::preloadImageFor(const QString &emoji) { + if (_images.contains(emoji)) { + return; + } + auto &set = _images.emplace(emoji).first->second; + const auto i = ranges::find(_available, emoji, &Reaction::emoji); + const auto document = (i != end(_available)) + ? i->staticIcon.get() + : nullptr; + if (document) { + loadImage(set, document); + } else if (!_waitingForList) { + refresh(); + } +} + +QImage Reactions::resolveImageFor( + const QString &emoji, + ImageSize size) { + const auto i = _images.find(emoji); + if (i == end(_images)) { + preloadImageFor(emoji); + } + auto &set = (i != end(_images)) ? i->second : _images[emoji]; + switch (size) { + case ImageSize::BottomInfo: return set.bottomInfo; + case ImageSize::InlineList: return set.inlineList; + } + Unexpected("ImageSize in Reactions::resolveImageFor."); +} + +void Reactions::resolveImages() { + for (auto &[emoji, set] : _images) { + if (!set.bottomInfo.isNull() || set.media) { + continue; + } + const auto i = ranges::find(_available, emoji, &Reaction::emoji); + const auto document = (i != end(list())) + ? i->staticIcon.get() + : nullptr; + if (document) { + loadImage(set, document); + } else { + LOG(("API Error: Reaction for emoji '%1' not found!" + ).arg(emoji)); + } + } +} + +void Reactions::loadImage( + ImageSet &set, + not_null document) { + if (!set.bottomInfo.isNull()) { + return; + } else if (!set.media) { + set.media = document->createMediaView(); + } + if (const auto image = set.media->getStickerLarge()) { + setImage(set, image->original()); + } else if (!_imagesLoadLifetime) { + document->session().downloaderTaskFinished( + ) | rpl::start_with_next([=] { + downloadTaskFinished(); + }, _imagesLoadLifetime); + } +} + +void Reactions::setImage(ImageSet &set, QImage large) { + set.media = nullptr; + const auto scale = [&](int size) { + const auto factor = style::DevicePixelRatio(); + return Images::prepare( + large, + size * factor, + size * factor, + Images::Option::Smooth, + size, + size); + }; + set.bottomInfo = scale(st::reactionInfoSize); + set.inlineList = scale(st::reactionBottomSize); +} + +void Reactions::downloadTaskFinished() { + auto hasOne = false; + for (auto &[emoji, set] : _images) { + if (!set.media) { + continue; + } else if (const auto image = set.media->getStickerLarge()) { + setImage(set, image->original()); + } else { + hasOne = true; + } + } + if (!hasOne) { + _imagesLoadLifetime.destroy(); + } +} + std::vector Reactions::Filtered( const std::vector &reactions, const std::vector &emoji) { @@ -101,6 +203,10 @@ void Reactions::request() { _available.push_back(*parsed); } } + if (_waitingForList) { + _waitingForList = false; + resolveImages(); + } _updated.fire({}); }, [&](const MTPDmessages_availableReactionsNotModified &) { }); diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 2209bb174..e5787fc25 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { +class DocumentMedia; class Session; struct Reaction { @@ -40,12 +41,32 @@ public: [[nodiscard]] rpl::producer<> updates() const; + enum class ImageSize { + BottomInfo, + InlineList, + }; + void preloadImageFor(const QString &emoji); + [[nodiscard]] QImage resolveImageFor( + const QString &emoji, + ImageSize size); + private: + struct ImageSet { + QImage bottomInfo; + QImage inlineList; + std::shared_ptr media; + }; + void request(); [[nodiscard]] std::optional parse( const MTPAvailableReaction &entry); + void loadImage(ImageSet &set, not_null document); + void setImage(ImageSet &set, QImage large); + void resolveImages(); + void downloadTaskFinished(); + const not_null _owner; std::vector _available; @@ -54,6 +75,10 @@ private: mtpRequestId _requestId = 0; int32 _hash = 0; + base::flat_map _images; + rpl::lifetime _imagesLoadLifetime; + bool _waitingForList = false; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index c2e2a5110..fa7f77d7c 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -18,17 +18,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_message.h" #include "history/view/history_view_cursor_state.h" #include "data/data_message_reactions.h" -#include "data/data_session.h" -#include "data/data_document.h" -#include "data/data_document_media.h" -#include "main/main_session.h" #include "styles/style_chat.h" #include "styles/style_dialogs.h" namespace HistoryView { -BottomInfo::BottomInfo(Data &&data) -: _data(std::move(data)) { +BottomInfo::BottomInfo( + not_null<::Data::Reactions*> reactionsOwner, + Data &&data) +: _reactionsOwner(reactionsOwner) +, _data(std::move(data)) { layout(); } @@ -227,6 +226,11 @@ void BottomInfo::paintReactions( y += st::msgDateFont->height; widthLeft = availableWidth; } + if (reaction.image.isNull()) { + reaction.image = _reactionsOwner->resolveImageFor( + reaction.emoji, + ::Data::Reactions::ImageSize::BottomInfo); + } if (!reaction.image.isNull()) { p.drawImage( x, @@ -362,56 +366,10 @@ QSize BottomInfo::countOptimalSize() { BottomInfo::Reaction BottomInfo::prepareReactionWithEmoji( const QString &emoji) { auto result = Reaction{ .emoji = emoji }; - auto &reactions = _data.owner->reactions(); - const auto &list = reactions.list(); - const auto i = ranges::find( - list, - emoji, - &::Data::Reaction::emoji); - const auto document = (i != end(list)) - ? i->staticIcon.get() - : nullptr; - if (document) { - loadReactionImage(result, document); - } else if (!_waitingForReactionsList) { - reactions.refresh(); - reactions.updates( - ) | rpl::filter([=] { - return _waitingForReactionsList; - }) | rpl::start_with_next([=] { - reactionsListLoaded(); - }, _assetsLoadLifetime); - } + _reactionsOwner->preloadImageFor(emoji); return result; } -void BottomInfo::reactionsListLoaded() { - _waitingForReactionsList = false; - if (assetsLoaded()) { - _assetsLoadLifetime.destroy(); - } - - const auto &list = _data.owner->reactions().list(); - for (auto &reaction : _reactions) { - if (!reaction.image.isNull() || reaction.media) { - continue; - } - const auto i = ranges::find( - list, - reaction.emoji, - &::Data::Reaction::emoji); - const auto document = (i != end(list)) - ? i->staticIcon.get() - : nullptr; - if (document) { - loadReactionImage(reaction, document); - } else { - LOG(("API Error: Reaction for emoji '%1' not found!" - ).arg(reaction.emoji)); - } - } -} - void BottomInfo::setReactionCount(Reaction &reaction, int count) { if (reaction.count == count) { return; @@ -425,72 +383,14 @@ void BottomInfo::setReactionCount(Reaction &reaction, int count) { : 0; } -void BottomInfo::loadReactionImage( - Reaction &reaction, - not_null document) { - if (!reaction.image.isNull()) { - return; - } else if (!reaction.media) { - reaction.media = document->createMediaView(); - } - if (const auto image = reaction.media->getStickerLarge()) { - setReactionImage(reaction, image->original()); - } else if (!_waitingForDownloadTask) { - _waitingForDownloadTask = true; - document->session().downloaderTaskFinished( - ) | rpl::start_with_next([=] { - downloadTaskFinished(); - }, _assetsLoadLifetime); - } -} - -void BottomInfo::setReactionImage(Reaction &reaction, QImage large) { - reaction.media = nullptr; - const auto size = st::reactionInfoSize; - const auto factor = style::DevicePixelRatio(); - reaction.image = Images::prepare( - std::move(large), - size * factor, - size * factor, - Images::Option::Smooth, - size, - size); -} - -void BottomInfo::downloadTaskFinished() { - auto hasOne = false; - for (auto &reaction : _reactions) { - if (!reaction.media) { - continue; - } else if (const auto image = reaction.media->getStickerLarge()) { - setReactionImage(reaction, image->original()); - } else { - hasOne = true; - } - } - if (!hasOne) { - _waitingForDownloadTask = false; - if (assetsLoaded()) { - _assetsLoadLifetime.destroy(); - } - } -} - -bool BottomInfo::assetsLoaded() const { - return !_waitingForReactionsList && !_waitingForDownloadTask; -} - BottomInfo::Data BottomInfoDataFromMessage(not_null message) { using Flag = BottomInfo::Data::Flag; - - const auto owner = &message->data()->history()->owner(); - auto result = BottomInfo::Data{ .owner = owner }; - const auto item = message->message(); + + auto result = BottomInfo::Data(); result.date = message->dateTime(); if (message->embedReactionsInBottomInfo()) { result.reactions = item->reactions(); - result.chosenReaction = item->chosenReaction(); } if (message->hasOutLayout()) { result.flags |= Flag::OutLayout; diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index 49eec94c9..573b52710 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -16,8 +16,7 @@ struct ChatPaintContext; } // namespace Ui namespace Data { -class Session; -class DocumentMedia; +class Reactions; } // namespace Data namespace HistoryView { @@ -41,16 +40,14 @@ public: friend inline constexpr bool is_flag_type(Flag) { return true; }; using Flags = base::flags; - not_null<::Data::Session*> owner; QDateTime date; QString author; base::flat_map reactions; - QString chosenReaction; std::optional views; std::optional replies; Flags flags; }; - explicit BottomInfo(Data &&data); + BottomInfo(not_null<::Data::Reactions*> reactionsOwner, Data &&data); void update(Data &&data, int availableWidth); @@ -70,9 +67,8 @@ public: private: struct Reaction { - QImage image; + mutable QImage image; QString emoji; - std::shared_ptr<::Data::DocumentMedia> media; QString countText; int count = 0; int countTextWidth = 0; @@ -96,14 +92,9 @@ private: QSize countCurrentSize(int newWidth) override; void setReactionCount(Reaction &reaction, int count); - void loadReactionImage(Reaction &reaction, not_null document); - void setReactionImage(Reaction &reaction, QImage large); [[nodiscard]] Reaction prepareReactionWithEmoji(const QString &emoji); - void reactionsListLoaded(); - void downloadTaskFinished(); - [[nodiscard]] bool assetsLoaded() const; - + const not_null<::Data::Reactions*> _reactionsOwner; Data _data; Ui::Text::String _authorEditedDate; Ui::Text::String _views; @@ -111,11 +102,6 @@ private: std::vector _reactions; int _reactionsMaxWidth = 0; int _dateWidth = 0; - - rpl::lifetime _assetsLoadLifetime; - bool _waitingForReactionsList = false; - bool _waitingForDownloadTask = false; - bool _authorElided = false; }; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 587f0d07c..be8cfc430 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -245,7 +245,9 @@ Message::Message( not_null data, Element *replacing) : Element(delegate, data, replacing) -, _bottomInfo(BottomInfoDataFromMessage(this)) { +, _bottomInfo( + &data->history()->owner().reactions(), + BottomInfoDataFromMessage(this)) { initLogEntryOriginal(); initPsa(); refreshReactions(); @@ -1924,7 +1926,9 @@ void Message::refreshReactions() { using namespace Reactions; auto data = InlineListDataFromMessage(this); if (!_reactions) { - _reactions = std::make_unique(std::move(data)); + _reactions = std::make_unique( + &item->history()->owner().reactions(), + std::move(data)); } else { _reactions->update(std::move(data), width()); } diff --git a/Telegram/SourceFiles/history/view/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/history_view_reactions.cpp index 4fc000e3e..c567a7baa 100644 --- a/Telegram/SourceFiles/history/view/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reactions.cpp @@ -10,11 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_message.h" #include "history/history.h" #include "history/view/history_view_message.h" -#include "data/data_document.h" -#include "data/data_document_media.h" #include "data/data_message_reactions.h" -#include "data/data_session.h" -#include "main/main_session.h" #include "lang/lang_tag.h" #include "ui/chat/chat_style.h" #include "styles/style_chat.h" @@ -27,8 +23,9 @@ constexpr auto kOutNonChosenOpacity = 0.18; } // namespace -InlineList::InlineList(Data &&data) -: _data(std::move(data)) { +InlineList::InlineList(not_null<::Data::Reactions*> owner, Data &&data) +: _owner(owner) +, _data(std::move(data)) { layout(); } @@ -79,56 +76,10 @@ void InlineList::layoutButtons() { InlineList::Button InlineList::prepareButtonWithEmoji(const QString &emoji) { auto result = Button{ .emoji = emoji }; - auto &reactions = _data.owner->reactions(); - const auto &list = reactions.list(); - const auto i = ranges::find( - list, - emoji, - &::Data::Reaction::emoji); - const auto document = (i != end(list)) - ? i->staticIcon.get() - : nullptr; - if (document) { - loadButtonImage(result, document); - } else if (!_waitingForReactionsList) { - reactions.refresh(); - reactions.updates( - ) | rpl::filter([=] { - return _waitingForReactionsList; - }) | rpl::start_with_next([=] { - reactionsListLoaded(); - }, _assetsLoadLifetime); - } + _owner->preloadImageFor(emoji); return result; } -void InlineList::reactionsListLoaded() { - _waitingForReactionsList = false; - if (assetsLoaded()) { - _assetsLoadLifetime.destroy(); - } - - const auto &list = _data.owner->reactions().list(); - for (auto &button : _buttons) { - if (!button.image.isNull() || button.media) { - continue; - } - const auto i = ranges::find( - list, - button.emoji, - &::Data::Reaction::emoji); - const auto document = (i != end(list)) - ? i->staticIcon.get() - : nullptr; - if (document) { - loadButtonImage(button, document); - } else { - LOG(("API Error: Reaction for emoji '%1' not found!" - ).arg(button.emoji)); - } - } -} - void InlineList::setButtonCount(Button &button, int count) { if (button.count == count) { return; @@ -138,61 +89,6 @@ void InlineList::setButtonCount(Button &button, int count) { button.countTextWidth = st::semiboldFont->width(button.countText); } -void InlineList::loadButtonImage( - Button &button, - not_null document) { - if (!button.image.isNull()) { - return; - } else if (!button.media) { - button.media = document->createMediaView(); - } - if (const auto image = button.media->getStickerLarge()) { - setButtonImage(button, image->original()); - } else if (!_waitingForDownloadTask) { - _waitingForDownloadTask = true; - document->session().downloaderTaskFinished( - ) | rpl::start_with_next([=] { - downloadTaskFinished(); - }, _assetsLoadLifetime); - } -} - -void InlineList::setButtonImage(Button &button, QImage large) { - button.media = nullptr; - const auto size = st::reactionBottomSize; - const auto factor = style::DevicePixelRatio(); - button.image = Images::prepare( - std::move(large), - size * factor, - size * factor, - Images::Option::Smooth, - size, - size); -} - -void InlineList::downloadTaskFinished() { - auto hasOne = false; - for (auto &button : _buttons) { - if (!button.media) { - continue; - } else if (const auto image = button.media->getStickerLarge()) { - setButtonImage(button, image->original()); - } else { - hasOne = true; - } - } - if (!hasOne) { - _waitingForDownloadTask = false; - if (assetsLoaded()) { - _assetsLoadLifetime.destroy(); - } - } -} - -bool InlineList::assetsLoaded() const { - return !_waitingForReactionsList && !_waitingForDownloadTask; -} - QSize InlineList::countOptimalSize() { if (_buttons.empty()) { return _skipBlock; @@ -282,7 +178,14 @@ void InlineList::paint( p.setOpacity(1.); } } - p.drawImage(inner.topLeft(), button.image); + if (button.image.isNull()) { + button.image = _owner->resolveImageFor( + button.emoji, + ::Data::Reactions::ImageSize::InlineList); + } + if (!button.image.isNull()) { + p.drawImage(inner.topLeft(), button.image); + } p.setPen(!inbubble ? st->msgServiceFg() : !chosen @@ -304,11 +207,10 @@ void InlineList::paint( } InlineListData InlineListDataFromMessage(not_null message) { - const auto owner = &message->data()->history()->owner(); - auto result = InlineListData{ .owner = owner }; - using Flag = InlineListData::Flag; const auto item = message->message(); + + auto result = InlineListData(); result.reactions = item->reactions(); result.chosenReaction = item->chosenReaction(); result.flags = (message->hasOutLayout() ? Flag::OutLayout : Flag()) diff --git a/Telegram/SourceFiles/history/view/history_view_reactions.h b/Telegram/SourceFiles/history/view/history_view_reactions.h index 9ac27c9be..b9f0a125e 100644 --- a/Telegram/SourceFiles/history/view/history_view_reactions.h +++ b/Telegram/SourceFiles/history/view/history_view_reactions.h @@ -9,11 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_object.h" -class DocumentData; - namespace Data { -class Session; -class DocumentMedia; +class Reactions; } // namespace Data namespace Ui { @@ -35,7 +32,6 @@ struct InlineListData { friend inline constexpr bool is_flag_type(Flag) { return true; }; using Flags = base::flags; - not_null owner; base::flat_map reactions; QString chosenReaction; Flags flags = {}; @@ -44,7 +40,7 @@ struct InlineListData { class InlineList final : public Object { public: using Data = InlineListData; - explicit InlineList(Data &&data); + InlineList(not_null<::Data::Reactions*> owner, Data &&data); void update(Data &&data, int availableWidth); QSize countCurrentSize(int newWidth) override; @@ -61,9 +57,8 @@ public: private: struct Button { QRect geometry; - QImage image; + mutable QImage image; QString emoji; - std::shared_ptr<::Data::DocumentMedia> media; ClickHandlerPtr link; QString countText; int count = 0; @@ -74,24 +69,15 @@ private: void layoutButtons(); void setButtonCount(Button &button, int count); - void loadButtonImage(Button &button, not_null document); - void setButtonImage(Button &button, QImage large); [[nodiscard]] Button prepareButtonWithEmoji(const QString &emoji); - void reactionsListLoaded(); - void downloadTaskFinished(); - [[nodiscard]] bool assetsLoaded() const; - QSize countOptimalSize() override; + const not_null<::Data::Reactions*> _owner; Data _data; std::vector