From dc4e5185edf56c0ebab0946949d65b8e2a726639 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 24 Dec 2025 12:48:09 +0300 Subject: [PATCH] Added initial implementation of smart stickers search. --- .../chat_helpers/stickers_list_widget.cpp | 166 ++++++++++++++---- .../chat_helpers/stickers_list_widget.h | 10 +- .../chat_helpers/tabbed_selector.cpp | 7 +- .../chat_helpers/tabbed_selector.h | 3 +- 4 files changed, 150 insertions(+), 36 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index ce15597c63..c82aecf8b0 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -552,38 +552,56 @@ int StickersListWidget::countDesiredHeight(int newWidth) { } void StickersListWidget::sendSearchRequest() { - if (_searchRequestId || _searchNextQuery.isEmpty() || _isEffects) { + if (_searchSetsRequestId + || _searchStickersRequestId + || _searchNextQuery.isEmpty() + || _isEffects) { return; } _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery; - auto it = _searchCache.find(_searchQuery); - if (it != _searchCache.cend()) { + auto it = _searchStickersCache.find(_searchQuery); + if (it != _searchStickersCache.cend()) { toggleSearchLoading(false); return; } toggleSearchLoading(true); if (_searchQuery == Ui::PremiumGroupFakeEmoticon()) { toggleSearchLoading(false); - _searchRequestId = 0; - _searchCache.emplace(_searchQuery, std::vector()); + _searchSetsRequestId = 0; + _searchSetsCache.emplace(_searchQuery, std::vector()); + _searchStickersCache.emplace(_searchQuery, std::vector()); showSearchResults(); return; } const auto hash = uint64(0); - _searchRequestId = _api.request(MTPmessages_SearchStickerSets( + _searchStickersRequestId = _api.request(MTPmessages_SearchStickers( MTP_flags(0), MTP_string(_searchQuery), + MTPstring(), // emoticon + MTP_vector(), // lang_code + MTP_int(0), // offset + MTP_int(50), // limit MTP_long(hash) - )).done([=](const MTPmessages_FoundStickerSets &result) { - searchResultsDone(result); + )).done([=](const MTPmessages_FoundStickers &result) { + searchStickersResultsDone(result); }).fail([=] { - // show error? - toggleSearchLoading(false); - _searchRequestId = 0; + _searchStickersRequestId = 0; + _searchStickersCache.emplace(_searchQuery, std::vector()); + + _searchSetsRequestId = _api.request(MTPmessages_SearchStickerSets( + MTP_flags(0), + MTP_string(_searchQuery), + MTP_long(hash) + )).done([=](const MTPmessages_FoundStickerSets &result) { + searchResultsDone(result); + }).fail([=] { + toggleSearchLoading(false); + _searchSetsRequestId = 0; + }).handleAllErrors().send(); }).handleAllErrors().send(); } @@ -609,10 +627,14 @@ void StickersListWidget::searchForSets( } if (_searchQuery != cleaned) { toggleSearchLoading(false); - if (const auto requestId = base::take(_searchRequestId)) { + if (const auto requestId = base::take(_searchSetsRequestId)) { _api.request(requestId).cancel(); } - if (_searchCache.find(cleaned) != _searchCache.cend()) { + if (const auto requestId = base::take(_searchStickersRequestId)) { + _api.request(requestId).cancel(); + } + if (_searchStickersCache.find(cleaned) != _searchStickersCache.cend() + || _searchSetsCache.find(cleaned) != _searchSetsCache.cend()) { _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery = cleaned; } else { @@ -625,14 +647,18 @@ void StickersListWidget::searchForSets( void StickersListWidget::cancelSetsSearch() { toggleSearchLoading(false); - if (const auto requestId = base::take(_searchRequestId)) { + if (const auto requestId = base::take(_searchSetsRequestId)) { + _api.request(requestId).cancel(); + } + if (const auto requestId = base::take(_searchStickersRequestId)) { _api.request(requestId).cancel(); } _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery = QString(); _filteredStickers.clear(); _filterStickersCornerEmoji.clear(); - _searchCache.clear(); + _searchSetsCache.clear(); + _searchStickersCache.clear(); refreshSearchRows(nullptr); } @@ -642,8 +668,8 @@ void StickersListWidget::showSearchResults() { } void StickersListWidget::refreshSearchRows() { - auto it = _searchCache.find(_searchQuery); - auto sets = (it != end(_searchCache)) + auto it = _searchSetsCache.find(_searchQuery); + auto sets = (it != end(_searchSetsCache)) ? &it->second : nullptr; refreshSearchRows(sets); @@ -661,10 +687,20 @@ void StickersListWidget::refreshSearchRows( } }); + const auto foundStickersIt = _searchStickersCache.find(_searchNextQuery); + const auto hasCloudFoundStickers = true + && (foundStickersIt != _searchStickersCache.end()) + && !foundStickersIt->second.empty(); + fillFilteredStickersRow(); + if (!_isEffects) { fillLocalSearchRows(_searchNextQuery); } + + if (hasCloudFoundStickers) { + fillFoundStickersRow(foundStickersIt->second); + } if (!cloudSets && _searchNextQuery.isEmpty()) { showStickerSet(!_mySets.empty() ? _mySets[0].id @@ -733,6 +769,33 @@ void StickersListWidget::fillCloudSearchRows( } } +void StickersListWidget::fillFoundStickersRow( + const std::vector &stickerIds) { + if (stickerIds.empty()) { + return; + } + auto elements = std::vector(); + elements.reserve(stickerIds.size()); + for (const auto id : stickerIds) { + if (const auto document = session().data().document(id)) { + elements.push_back(Sticker{ document }); + } + } + if (elements.empty()) { + return; + } + + _searchSets.emplace_back( + SearchEmojiSectionSetId(), + nullptr, + Data::StickersSetFlag::Special, + QString(), + QString(), + elements.size(), + false, // externalLayout + std::move(elements)); +} + void StickersListWidget::fillFilteredStickersRow() { if (_filteredStickers.empty()) { return; @@ -774,6 +837,10 @@ void StickersListWidget::toggleSearchLoading(bool loading) { if (_search) { _search->setLoading(loading); } + if (_searchLoading != loading) { + _searchLoading = loading; + update(); + } } void StickersListWidget::takeHeavyData( @@ -822,7 +889,7 @@ void StickersListWidget::takeHeavyData(Set &to, Set &from) { } } for (const auto &sticker : fromList) { - if (sticker.lottie) { + if (sticker.lottie && to.lottiePlayer) { to.lottiePlayer->remove(sticker.lottie); } } @@ -855,10 +922,49 @@ auto StickersListWidget::shownSets() -> std::vector & { Unexpected("Section in StickersListWidget."); } +void StickersListWidget::searchStickersResultsDone( + const MTPmessages_FoundStickers &result) { + _searchStickersRequestId = 0; + + result.match([&](const MTPDmessages_foundStickersNotModified &data) { + LOG(("API: messages.foundStickersNotModified.")); + }, [&](const MTPDmessages_foundStickers &data) { + auto it = _searchStickersCache.find(_searchQuery); + if (it == _searchStickersCache.cend()) { + it = _searchStickersCache.emplace( + _searchQuery, + std::vector()).first; + } + + for (const auto &sticker : data.vstickers().v) { + if (const auto doc = session().data().processDocument(sticker)) { + it->second.push_back(doc->id); + } + } + + if (!it->second.empty()) { + toggleSearchLoading(false); + showSearchResults(); + } else { + const auto hash = uint64(0); + _searchSetsRequestId = _api.request(MTPmessages_SearchStickerSets( + MTP_flags(0), + MTP_string(_searchQuery), + MTP_long(hash) + )).done([=](const MTPmessages_FoundStickerSets &result) { + searchResultsDone(result); + }).fail([=] { + toggleSearchLoading(false); + _searchSetsRequestId = 0; + }).handleAllErrors().send(); + } + }); +} + void StickersListWidget::searchResultsDone( const MTPmessages_FoundStickerSets &result) { toggleSearchLoading(false); - _searchRequestId = 0; + _searchSetsRequestId = 0; if (result.type() == mtpc_messages_foundStickerSetsNotModified) { LOG(("API Error: " @@ -868,9 +974,9 @@ void StickersListWidget::searchResultsDone( Assert(result.type() == mtpc_messages_foundStickerSets); - auto it = _searchCache.find(_searchQuery); - if (it == _searchCache.cend()) { - it = _searchCache.emplace( + auto it = _searchSetsCache.find(_searchQuery); + if (it == _searchSetsCache.cend()) { + it = _searchSetsCache.emplace( _searchQuery, std::vector()).first; } @@ -933,7 +1039,14 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { const auto paused = On(PowerSaving::kStickersPanel) || this->paused(); if (sets.empty() && _section == Section::Search) { - paintEmptySearchResults(p); + const auto loading = _searchLoading || _searchRequestTimer.isActive(); + Inner::paintEmptySearchResults( + p, + st::stickersEmpty, + loading + ? tr::lng_contacts_loading(tr::now) + : tr::lng_stickers_nothing_found(tr::now), + loading); } const auto badgeText = tr::lng_stickers_creator_badge(tr::now); const auto &badgeFont = st::stickersHeaderBadgeFont; @@ -1213,13 +1326,6 @@ void StickersListWidget::pauseInvisibleLottieIn(const SectionInfo &info) { } } -void StickersListWidget::paintEmptySearchResults(Painter &p) { - Inner::paintEmptySearchResults( - p, - st::stickersEmpty, - tr::lng_stickers_nothing_found(tr::now)); -} - int StickersListWidget::megagroupSetInfoLeft() const { return st().headerLeft - st().margin.left(); } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index c436273fcf..87fc1a6733 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -286,7 +286,6 @@ private: bool paused, bool selected, bool deleteSelected); - void paintEmptySearchResults(Painter &p); void ensureLottiePlayer(Set &set); void setupLottie(Set &set, int section, int index); @@ -357,11 +356,13 @@ private: void cancelSetsSearch(); void showSearchResults(); void searchResultsDone(const MTPmessages_FoundStickerSets &result); + void searchStickersResultsDone(const MTPmessages_FoundStickers &result); void refreshSearchRows(); void refreshSearchRows(const std::vector *cloudSets); void fillFilteredStickersRow(); void fillLocalSearchRows(const QString &query); void fillCloudSearchRows(const std::vector &cloudSets); + void fillFoundStickersRow(const std::vector &stickerIds); void addSearchRow(not_null set); void toggleSearchLoading(bool loading); @@ -442,11 +443,14 @@ private: std::vector> _filteredStickers; std::vector _filterStickersCornerEmoji; rpl::variable _recentShownCount; - std::map> _searchCache; + std::map> _searchSetsCache; + std::map> _searchStickersCache; std::vector> _searchIndex; base::Timer _searchRequestTimer; QString _searchQuery, _searchNextQuery; - mtpRequestId _searchRequestId = 0; + mtpRequestId _searchSetsRequestId = 0; + mtpRequestId _searchStickersRequestId = 0; + bool _searchLoading = false; rpl::event_stream _chosen; rpl::event_stream<> _scrollUpdated; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index f0b62e8025..a44da34ace 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -1493,12 +1493,15 @@ void TabbedSelector::Inner::checkHideWithBox( void TabbedSelector::Inner::paintEmptySearchResults( Painter &p, const style::icon &icon, - const QString &text) const { + const QString &text, + bool skipIcon) const { const auto iconLeft = (width() - icon.width()) / 2; const auto iconTop = std::max( (height() / 3) - (icon.height() / 2), st::normalFont->height); - icon.paint(p, iconLeft, iconTop, width()); + if (!skipIcon) { + icon.paint(p, iconLeft, iconTop, width()); + } const auto textWidth = st::normalFont->width(text); const auto textTop = std::min( diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index 47e8c4b649..903da475b1 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -422,7 +422,8 @@ protected: void paintEmptySearchResults( Painter &p, const style::icon &icon, - const QString &text) const; + const QString &text, + bool skipIcon = false) const; private: const style::EmojiPan &_st;