Added support of pagination to stickers and emoji search.

This commit is contained in:
23rd
2026-04-13 08:33:51 +03:00
parent 9245c2e49b
commit de737b3cbe
4 changed files with 310 additions and 93 deletions
@@ -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<MTPstring>();
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<MTPstring>(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<DocumentId>());
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<MTPstring>(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<DocumentId>());
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<DocumentId>()).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<DocumentId>()).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<uint64>()).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(
@@ -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<RecentOne> _searchResults;
bool _searchMode = false;
std::map<QString, std::vector<DocumentId>> _searchCloudCache;
std::map<QString, int> _searchCloudNextOffset;
std::map<QString, std::vector<uint64>> _searchSetsCache;
std::vector<CustomSet> _searchSets;
QString _searchRequestQuery;
@@ -57,6 +57,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtWidgets/QApplication>
namespace ChatHelpers {
[[nodiscard]] QVector<MTPstring> SearchStickersLangCodes() {
auto result = QVector<MTPstring>();
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<MTPstring>(), // lang_code
MTP_int(0), // offset
MTP_int(50), // limit
MTP_vector<MTPstring>(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<DocumentId>());
_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<DocumentId>());
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<Set> & {
}
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<DocumentId>());
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<DocumentId>()).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<uint64>()).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();
}
});
}
@@ -57,6 +57,7 @@ struct FlatLabel;
namespace ChatHelpers {
extern const char kOptionUnlimitedRecentStickers[];
[[nodiscard]] QVector<MTPstring> 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<uint64> *cloudSets);
void fillFilteredStickersRow();
@@ -447,6 +461,7 @@ private:
rpl::variable<int> _recentShownCount;
std::map<QString, std::vector<uint64>> _searchSetsCache;
std::map<QString, std::vector<DocumentId>> _searchStickersCache;
std::map<QString, int> _searchStickersNextOffset;
std::vector<std::pair<uint64, QStringList>> _searchIndex;
base::Timer _searchRequestTimer;
QString _searchQuery, _searchNextQuery;