From de737b3cbe494b1d0cfcd4b3eaf0fcfdc27afff5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 13 Apr 2026 08:33:51 +0300 Subject: [PATCH] Added support of pagination to stickers and emoji search. --- .../chat_helpers/emoji_list_widget.cpp | 183 +++++++++++++----- .../chat_helpers/emoji_list_widget.h | 18 +- .../chat_helpers/stickers_list_widget.cpp | 183 ++++++++++++++---- .../chat_helpers/stickers_list_widget.h | 19 +- 4 files changed, 310 insertions(+), 93 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 33b7c96b1f..3957bd2e75 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -64,8 +64,10 @@ namespace { constexpr auto kCollapsedRows = 3; constexpr auto kAppearDuration = 0.3; constexpr auto kCustomSearchLimit = 256; +constexpr auto kCloudSearchPageLimit = 50; constexpr auto kColorPickerDelay = crl::time(500); constexpr auto kSearchRequestDelay = 400; +constexpr auto kPreloadSearchPages = 4; using Core::RecentEmojiId; using Core::RecentEmojiDocument; @@ -707,13 +709,13 @@ void EmojiListWidget::applyNextSearchQuery() { _api.request(requestId).cancel(); } _searchNextRequestQuery = _searchQueryText; - const auto cloudCached = _searchCloudCache.find(_searchQueryText) + _searchRequestQuery = _searchQueryText; + const auto cloudCached = _searchCloudCache.find(_searchRequestQuery) != _searchCloudCache.cend(); - const auto setsCached = _searchSetsCache.find(_searchQueryText) + const auto setsCached = _searchSetsCache.find(_searchRequestQuery) != _searchSetsCache.cend(); if (cloudCached || setsCached) { _searchRequestTimer.cancel(); - _searchRequestQuery = _searchQueryText; fillCloudSearchResults(); fillCloudSearchSets(); if (!cloudCached || !setsCached) { @@ -887,74 +889,76 @@ void EmojiListWidget::toggleSearchLoading(bool loading) { } void EmojiListWidget::sendSearchRequest() { - _searchRequestQuery = _searchQueryText; + _searchRequestQuery = _searchNextRequestQuery; if (_searchRequestQuery.isEmpty()) { return; } + const auto query = _searchRequestQuery; const auto cloudCached = _searchCloudCache.find( - _searchRequestQuery) != _searchCloudCache.cend(); + query) != _searchCloudCache.cend(); const auto setsCached = _searchSetsCache.find( - _searchRequestQuery) != _searchSetsCache.cend(); + query) != _searchSetsCache.cend(); if (cloudCached && setsCached) { toggleSearchLoading(false); return; } toggleSearchLoading(true); - const auto hash = uint64(0); if (!cloudCached) { - auto langCodes = QVector(); - const auto method = QGuiApplication::inputMethod(); - if (method) { - for (const auto &lang : method->locale().uiLanguages()) { - langCodes.push_back(MTP_string(lang)); - } - } - using Flag = MTPmessages_SearchStickers::Flag; - _searchCloudRequestId = _api.request(MTPmessages_SearchStickers( - MTP_flags(Flag::f_emojis), - MTP_string(_searchRequestQuery), - MTP_string(_searchEmoticon), - MTP_vector(langCodes), - MTP_int(0), - MTP_int(50), - MTP_long(hash) - )).done([=](const MTPmessages_FoundStickers &result) { - searchCloudResultsDone(result); - }).fail([=] { - _searchCloudRequestId = 0; - _searchCloudCache.emplace( - _searchRequestQuery, - std::vector()); - if (!_searchSetsRequestId) { - toggleSearchLoading(false); - showSearchResults(); - } - }).handleAllErrors().send(); + requestSearchCloud(query, 0, true); } if (!setsCached) { - sendSearchSetsRequest(); + sendSearchSetsRequest(query); } } -void EmojiListWidget::sendSearchSetsRequest() { +void EmojiListWidget::sendSearchSetsRequest(const QString &query) { const auto hash = uint64(0); _searchSetsRequestId = _api.request( MTPmessages_SearchEmojiStickerSets( MTP_flags(0), - MTP_string(_searchRequestQuery), + MTP_string(query), MTP_long(hash)) ).done([=](const MTPmessages_FoundStickerSets &result) { - searchSetsResultsDone(result); + searchSetsResultsDone(query, result); }).fail([=] { _searchSetsRequestId = 0; - if (!_searchCloudRequestId) { + if ((_searchRequestQuery == query) && !_searchCloudRequestId) { toggleSearchLoading(false); } }).handleAllErrors().send(); } +void EmojiListWidget::requestSearchCloud( + const QString &query, + int offset, + bool fallbackToEmpty) { + using Flag = MTPmessages_SearchStickers::Flag; + const auto hash = uint64(0); + _searchCloudRequestId = _api.request(MTPmessages_SearchStickers( + MTP_flags(Flag::f_emojis), + MTP_string(query), + MTP_string(_searchEmoticon), + MTP_vector(SearchStickersLangCodes()), + MTP_int(offset), + MTP_int(kCloudSearchPageLimit), + MTP_long(hash) + )).done([=](const MTPmessages_FoundStickers &result) { + searchCloudResultsDone(query, offset, result); + }).fail([=] { + _searchCloudRequestId = 0; + if (!fallbackToEmpty) { + return; + } + _searchCloudCache.emplace(query, std::vector()); + if ((_searchRequestQuery == query) && !_searchSetsRequestId) { + toggleSearchLoading(false); + showSearchResults(); + } + }).handleAllErrors().send(); +} + void EmojiListWidget::cancelSearchRequest() { toggleSearchLoading(false); if (const auto requestId = base::take(_searchCloudRequestId)) { @@ -967,21 +971,48 @@ void EmojiListWidget::cancelSearchRequest() { _searchRequestQuery = QString(); _searchNextRequestQuery = QString(); _searchCloudCache.clear(); + _searchCloudNextOffset.clear(); _searchSetsCache.clear(); _searchSets.clear(); } void EmojiListWidget::searchCloudResultsDone( + const QString &query, + int requestedOffset, const MTPmessages_FoundStickers &result) { _searchCloudRequestId = 0; + const auto active = (_searchRequestQuery == query); - result.match([&](const MTPDmessages_foundStickersNotModified &) { + result.match([&](const MTPDmessages_foundStickersNotModified &data) { LOG(("API: messages.foundStickersNotModified.")); - }, [&](const MTPDmessages_foundStickers &data) { - auto it = _searchCloudCache.find(_searchRequestQuery); + auto it = _searchCloudCache.find(query); if (it == _searchCloudCache.cend()) { it = _searchCloudCache.emplace( - _searchRequestQuery, + query, + std::vector()).first; + } + if (const auto next = data.vnext_offset()) { + if (next->v > requestedOffset) { + _searchCloudNextOffset[query] = next->v; + } else { + _searchCloudNextOffset.erase(query); + } + } else { + _searchCloudNextOffset.erase(query); + } + if (!active) { + return; + } + if (!_searchSetsRequestId) { + toggleSearchLoading(false); + } + showSearchResults(); + checkPaginateSearchCloud(getVisibleTop(), getVisibleBottom()); + }, [&](const MTPDmessages_foundStickers &data) { + auto it = _searchCloudCache.find(query); + if (it == _searchCloudCache.cend()) { + it = _searchCloudCache.emplace( + query, std::vector()).first; } @@ -992,17 +1023,67 @@ void EmojiListWidget::searchCloudResultsDone( } } + if (const auto next = data.vnext_offset()) { + if (next->v > requestedOffset) { + _searchCloudNextOffset[query] = next->v; + } else { + _searchCloudNextOffset.erase(query); + } + } else { + _searchCloudNextOffset.erase(query); + } + + if (!active) { + return; + } + if (!_searchSetsRequestId) { toggleSearchLoading(false); } showSearchResults(); + checkPaginateSearchCloud( + getVisibleTop(), + getVisibleBottom()); }); } +void EmojiListWidget::loadMoreSearchCloud() { + if (_searchCloudRequestId + || _searchRequestQuery.isEmpty() + || (_searchRequestQuery != _searchNextRequestQuery)) { + return; + } + const auto query = _searchRequestQuery; + const auto offsetIt = _searchCloudNextOffset.find(query); + if (offsetIt == _searchCloudNextOffset.end()) { + return; + } + requestSearchCloud(query, offsetIt->second, false); +} + +void EmojiListWidget::checkPaginateSearchCloud( + int visibleTop, + int visibleBottom) { + if (!_searchMode + || _searchRequestQuery.isEmpty() + || (_searchRequestQuery != _searchNextRequestQuery) + || _searchCloudRequestId) { + return; + } + const auto visibleHeight = visibleBottom - visibleTop; + if (visibleHeight <= 0) { + return; + } + if (visibleBottom > height() - visibleHeight * kPreloadSearchPages) { + loadMoreSearchCloud(); + } +} + void EmojiListWidget::searchSetsResultsDone( + const QString &query, const MTPmessages_FoundStickerSets &result) { _searchSetsRequestId = 0; - if (!_searchCloudRequestId) { + if ((_searchRequestQuery == query) && !_searchCloudRequestId) { toggleSearchLoading(false); } @@ -1010,10 +1091,10 @@ void EmojiListWidget::searchSetsResultsDone( LOG(("API Error: " "messages.foundStickerSetsNotModified not expected.")); }, [&](const MTPDmessages_foundStickerSets &data) { - auto it = _searchSetsCache.find(_searchRequestQuery); + auto it = _searchSetsCache.find(query); if (it == _searchSetsCache.cend()) { it = _searchSetsCache.emplace( - _searchRequestQuery, + query, std::vector()).first; } for (const auto &setData : data.vsets().v) { @@ -1024,7 +1105,9 @@ void EmojiListWidget::searchSetsResultsDone( } it->second.push_back(set->id); } - showSearchResults(); + if (_searchRequestQuery == query) { + showSearchResults(); + } }); } @@ -1066,9 +1149,6 @@ void EmojiListWidget::fillCloudSearchResults() { } const auto test = session().isTestMode(); for (const auto id : it->second) { - if (_searchResults.size() >= kCustomSearchLimit) { - break; - } if (!_searchCustomIds.emplace(id).second) { continue; } @@ -1264,6 +1344,7 @@ void EmojiListWidget::visibleTopBottomUpdated( ValidateIconAnimations::Full); } unloadNotSeenCustom(visibleTop, visibleBottom); + checkPaginateSearchCloud(visibleTop, visibleBottom); } void EmojiListWidget::unloadNotSeenCustom( diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index c8deb74bb7..0d0ad370da 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -301,11 +301,22 @@ private: void appendPremiumSearchResults(); void appendLocalPackSearchResults(); void sendSearchRequest(); - void sendSearchSetsRequest(); + void sendSearchSetsRequest(const QString &query); + void requestSearchCloud( + const QString &query, + int offset, + bool fallbackToEmpty); void cancelSearchRequest(); void toggleSearchLoading(bool loading); - void searchCloudResultsDone(const MTPmessages_FoundStickers &result); - void searchSetsResultsDone(const MTPmessages_FoundStickerSets &result); + void searchCloudResultsDone( + const QString &query, + int requestedOffset, + const MTPmessages_FoundStickers &result); + void loadMoreSearchCloud(); + void checkPaginateSearchCloud(int visibleTop, int visibleBottom); + void searchSetsResultsDone( + const QString &query, + const MTPmessages_FoundStickerSets &result); void showSearchResults(); void fillCloudSearchResults(); void fillCloudSearchSets(); @@ -484,6 +495,7 @@ private: std::vector _searchResults; bool _searchMode = false; std::map> _searchCloudCache; + std::map _searchCloudNextOffset; std::map> _searchSetsCache; std::vector _searchSets; QString _searchRequestQuery; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 78002e3ee0..79172d23ad 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -57,6 +57,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace ChatHelpers { + +[[nodiscard]] QVector SearchStickersLangCodes() { + auto result = QVector(); + if (const auto method = QGuiApplication::inputMethod()) { + for (const auto &lang : method->locale().uiLanguages()) { + result.push_back(MTP_string(lang)); + } + } + return result; +} + namespace { constexpr auto kSearchRequestDelay = 400; @@ -362,6 +373,9 @@ void StickersListWidget::visibleTopBottomUpdated( checkVisibleFeatured(visibleTop, visibleBottom); } else { checkVisibleLottie(); + if (_section == Section::Search) { + checkPaginateSearchStickers(visibleTop, visibleBottom); + } } if (_footer) { _footer->validateSelectedIcon( @@ -612,31 +626,52 @@ void StickersListWidget::sendSearchRequest() { return; } + requestSearchStickers(_searchQuery, 0, true); +} + +void StickersListWidget::sendSearchSetsRequest(const QString &query) { + const auto hash = uint64(0); + _searchSetsRequestId = _api.request(MTPmessages_SearchStickerSets( + MTP_flags(0), + MTP_string(query), + MTP_long(hash) + )).done([=](const MTPmessages_FoundStickerSets &result) { + searchResultsDone(query, result); + }).fail([=] { + _searchSetsRequestId = 0; + if (_searchNextQuery == query) { + toggleSearchLoading(false); + } + }).handleAllErrors().send(); +} + +void StickersListWidget::requestSearchStickers( + const QString &query, + int offset, + bool requestSetsOnEmpty) { const auto hash = uint64(0); _searchStickersRequestId = _api.request(MTPmessages_SearchStickers( MTP_flags(0), - MTP_string(_searchQuery), + MTP_string(query), MTPstring(), // emoticon - MTP_vector(), // lang_code - MTP_int(0), // offset - MTP_int(50), // limit + MTP_vector(SearchStickersLangCodes()), + MTP_int(offset), + MTP_int(50), MTP_long(hash) )).done([=](const MTPmessages_FoundStickers &result) { - searchStickersResultsDone(result); + searchStickersResultsDone( + query, + offset, + requestSetsOnEmpty, + result); }).fail([=] { _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(); + if (requestSetsOnEmpty) { + _searchStickersCache.emplace(query, std::vector()); + if (_searchNextQuery == query) { + sendSearchSetsRequest(query); + } + } }).handleAllErrors().send(); } @@ -694,6 +729,7 @@ void StickersListWidget::cancelSetsSearch() { _filterStickersCornerEmoji.clear(); _searchSetsCache.clear(); _searchStickersCache.clear(); + _searchStickersNextOffset.clear(); refreshSearchRows(nullptr); } @@ -703,7 +739,7 @@ void StickersListWidget::showSearchResults() { } void StickersListWidget::refreshSearchRows() { - auto it = _searchSetsCache.find(_searchQuery); + auto it = _searchSetsCache.find(_searchNextQuery); auto sets = (it != end(_searchSetsCache)) ? &it->second : nullptr; @@ -958,16 +994,41 @@ auto StickersListWidget::shownSets() -> std::vector & { } void StickersListWidget::searchStickersResultsDone( + const QString &query, + int requestedOffset, + bool requestSetsOnEmpty, const MTPmessages_FoundStickers &result) { _searchStickersRequestId = 0; + const auto active = (_searchNextQuery == query); result.match([&](const MTPDmessages_foundStickersNotModified &data) { LOG(("API: messages.foundStickersNotModified.")); + if (const auto next = data.vnext_offset()) { + if (next->v > requestedOffset) { + _searchStickersNextOffset[query] = next->v; + } else { + _searchStickersNextOffset.erase(query); + } + } else { + _searchStickersNextOffset.erase(query); + } + _searchStickersCache.emplace(query, std::vector()); + if (!active) { + return; + } + if (requestSetsOnEmpty) { + sendSearchSetsRequest(query); + return; + } + refreshSearchRows(); + checkPaginateSearchStickers( + getVisibleTop(), + getVisibleBottom()); }, [&](const MTPDmessages_foundStickers &data) { - auto it = _searchStickersCache.find(_searchQuery); + auto it = _searchStickersCache.find(query); if (it == _searchStickersCache.cend()) { it = _searchStickersCache.emplace( - _searchQuery, + query, std::vector()).first; } @@ -977,38 +1038,84 @@ void StickersListWidget::searchStickersResultsDone( } } - if (!it->second.empty()) { - toggleSearchLoading(false); + if (const auto next = data.vnext_offset()) { + if (next->v > requestedOffset) { + _searchStickersNextOffset[query] = next->v; + } else { + _searchStickersNextOffset.erase(query); + } + } else { + _searchStickersNextOffset.erase(query); + } + + if (!active) { + return; + } + if (requestSetsOnEmpty && it->second.empty()) { + sendSearchSetsRequest(query); + return; + } + toggleSearchLoading(false); + if (requestSetsOnEmpty) { 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(); + refreshSearchRows(); } + checkPaginateSearchStickers( + getVisibleTop(), + getVisibleBottom()); }); } +void StickersListWidget::loadMoreSearchStickers() { + if (_searchStickersRequestId + || _searchQuery.isEmpty() + || _isEffects + || (_searchQuery != _searchNextQuery)) { + return; + } + const auto query = _searchQuery; + const auto offsetIt = _searchStickersNextOffset.find(query); + if (offsetIt == _searchStickersNextOffset.end()) { + return; + } + requestSearchStickers(query, offsetIt->second, false); +} + +void StickersListWidget::checkPaginateSearchStickers( + int visibleTop, + int visibleBottom) { + if (_section != Section::Search + || _searchQuery.isEmpty() + || (_searchQuery != _searchNextQuery) + || _searchStickersRequestId) { + return; + } + const auto visibleHeight = visibleBottom - visibleTop; + if (visibleHeight <= 0) { + return; + } + if (visibleBottom > height() - visibleHeight * kPreloadOfficialPages) { + loadMoreSearchStickers(); + } +} + void StickersListWidget::searchResultsDone( + const QString &query, const MTPmessages_FoundStickerSets &result) { - toggleSearchLoading(false); + if (_searchNextQuery == query) { + toggleSearchLoading(false); + } _searchSetsRequestId = 0; result.match([&](const MTPDmessages_foundStickerSetsNotModified &data) { LOG(("API Error: " "messages.foundStickerSetsNotModified not expected.")); }, [&](const MTPDmessages_foundStickerSets &data) { - auto it = _searchSetsCache.find(_searchQuery); + auto it = _searchSetsCache.find(query); if (it == _searchSetsCache.cend()) { it = _searchSetsCache.emplace( - _searchQuery, + query, std::vector()).first; } for (const auto &setData : data.vsets().v) { @@ -1018,7 +1125,9 @@ void StickersListWidget::searchResultsDone( } it->second.push_back(set->id); } - showSearchResults(); + if (_searchNextQuery == query) { + showSearchResults(); + } }); } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index 2a10e629b6..ad9f514c24 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -57,6 +57,7 @@ struct FlatLabel; namespace ChatHelpers { extern const char kOptionUnlimitedRecentStickers[]; +[[nodiscard]] QVector SearchStickersLangCodes(); struct StickerIcon; enum class ValidateIconAnimations; @@ -357,8 +358,21 @@ private: void cancelSetsSearch(); void showSearchResults(); - void searchResultsDone(const MTPmessages_FoundStickerSets &result); - void searchStickersResultsDone(const MTPmessages_FoundStickers &result); + void sendSearchSetsRequest(const QString &query); + void searchResultsDone( + const QString &query, + const MTPmessages_FoundStickerSets &result); + void requestSearchStickers( + const QString &query, + int offset, + bool requestSetsOnEmpty); + void searchStickersResultsDone( + const QString &query, + int requestedOffset, + bool requestSetsOnEmpty, + const MTPmessages_FoundStickers &result); + void loadMoreSearchStickers(); + void checkPaginateSearchStickers(int visibleTop, int visibleBottom); void refreshSearchRows(); void refreshSearchRows(const std::vector *cloudSets); void fillFilteredStickersRow(); @@ -447,6 +461,7 @@ private: rpl::variable _recentShownCount; std::map> _searchSetsCache; std::map> _searchStickersCache; + std::map _searchStickersNextOffset; std::vector> _searchIndex; base::Timer _searchRequestTimer; QString _searchQuery, _searchNextQuery;