Added initial implementation of smart emoji search.

This commit is contained in:
23rd
2026-03-31 09:08:07 +03:00
parent 8f56ef4502
commit 94b8685f23
2 changed files with 563 additions and 22 deletions
@@ -65,6 +65,7 @@ constexpr auto kCollapsedRows = 3;
constexpr auto kAppearDuration = 0.3;
constexpr auto kCustomSearchLimit = 256;
constexpr auto kColorPickerDelay = crl::time(500);
constexpr auto kSearchRequestDelay = 400;
using Core::RecentEmojiId;
using Core::RecentEmojiDocument;
@@ -502,6 +503,7 @@ EmojiListWidget::EmojiListWidget(
&session(),
st::emojiPremiumLock))
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
, _searchRequestTimer([=] { sendSearchRequest(); })
, _picker(this, st())
, _showPickerTimer([=] { showPicker(); })
, _previewTimer([=] { showPreview(); }) {
@@ -647,6 +649,7 @@ void EmojiListWidget::applyNextSearchQuery() {
if (!searching) {
_searchResults.clear();
_searchCustomIds.clear();
_searchSets.clear();
}
resizeToWidth(width());
_recentShownCount = searching
@@ -659,6 +662,7 @@ void EmojiListWidget::applyNextSearchQuery() {
updateSelected();
};
if (_searchQuery.empty()) {
cancelSearchRequest();
finish(false);
return;
}
@@ -667,11 +671,13 @@ void EmojiListWidget::applyNextSearchQuery() {
if (_searchEmoji == _searchEmojiPrevious) {
return;
}
_searchEmoticon = QString();
for (const auto emoji : plain) {
_searchEmoticon += emoji->text();
}
_searchResults.clear();
_searchCustomIds.clear();
if (_mode != Mode::Full || session().premium()) {
appendPremiumSearchResults();
}
_searchSets.clear();
if (_mode == Mode::Full) {
for (const auto emoji : plain) {
_searchResults.push_back({
@@ -679,6 +685,44 @@ void EmojiListWidget::applyNextSearchQuery() {
});
}
}
if (_mode != Mode::Full || session().premium()) {
appendPremiumSearchResults();
}
if (_mode == Mode::Full) {
appendLocalPackSearchResults();
}
_searchQueryText = ranges::accumulate(
_searchQuery,
QString(),
[](QString a, const QString &b) {
return a.isEmpty() ? b : (a + ' ' + b);
}).trimmed();
if (!_searchQueryText.isEmpty()) {
toggleSearchLoading(false);
if (const auto requestId = base::take(_searchCloudRequestId)) {
_api.request(requestId).cancel();
}
if (const auto requestId = base::take(_searchSetsRequestId)) {
_api.request(requestId).cancel();
}
_searchNextRequestQuery = _searchQueryText;
const auto cloudCached = _searchCloudCache.find(_searchQueryText)
!= _searchCloudCache.cend();
const auto setsCached = _searchSetsCache.find(_searchQueryText)
!= _searchSetsCache.cend();
if (cloudCached || setsCached) {
_searchRequestTimer.cancel();
_searchRequestQuery = _searchQueryText;
fillCloudSearchResults();
fillCloudSearchSets();
if (!cloudCached || !setsCached) {
sendSearchRequest();
}
} else {
_searchRequestTimer.callOnce(kSearchRequestDelay);
}
}
}
void EmojiListWidget::showPreview() {
@@ -776,6 +820,338 @@ void EmojiListWidget::appendPremiumSearchResults() {
}
}
void EmojiListWidget::appendLocalPackSearchResults() {
const auto text = _searchQueryText.toLower();
if (text.isEmpty()) {
return;
}
const auto test = session().isTestMode();
const auto &sets = session().data().stickers().sets();
const auto processSet = [&](uint64 setId) {
const auto it = sets.find(setId);
if (it == sets.end()) {
return;
}
const auto set = it->second.get();
if (!(set->flags & Data::StickersSetFlag::Emoji)) {
return;
}
const auto title = set->title.toLower();
if (!title.startsWith(text)
&& !title.contains(u' ' + text)) {
return;
}
const auto &list = set->stickers.empty()
? set->covers
: set->stickers;
for (const auto document : list) {
if (_searchResults.size() >= kCustomSearchLimit) {
return;
}
const auto sticker = document->sticker();
if (!sticker) {
continue;
}
const auto id = document->id;
if (!_searchCustomIds.emplace(id).second) {
continue;
}
const auto statusId = EmojiStatusId{ id };
_searchResults.push_back({
.custom = resolveCustomEmoji(
statusId,
document,
SearchEmojiSectionSetId()),
.id = { RecentEmojiDocument{ .id = id, .test = test } },
});
}
};
for (const auto setId
: session().data().stickers().emojiSetsOrder()) {
processSet(setId);
}
for (const auto setId
: session().data().stickers().featuredEmojiSetsOrder()) {
processSet(setId);
}
}
void EmojiListWidget::toggleSearchLoading(bool loading) {
if (_search) {
_search->setLoading(loading);
}
if (_searchLoading != loading) {
_searchLoading = loading;
update();
}
}
void EmojiListWidget::sendSearchRequest() {
_searchRequestQuery = _searchQueryText;
if (_searchRequestQuery.isEmpty()) {
return;
}
const auto cloudCached = _searchCloudCache.find(
_searchRequestQuery) != _searchCloudCache.cend();
const auto setsCached = _searchSetsCache.find(
_searchRequestQuery) != _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();
}
if (!setsCached) {
sendSearchSetsRequest();
}
}
void EmojiListWidget::sendSearchSetsRequest() {
const auto hash = uint64(0);
_searchSetsRequestId = _api.request(
MTPmessages_SearchEmojiStickerSets(
MTP_flags(0),
MTP_string(_searchRequestQuery),
MTP_long(hash))
).done([=](const MTPmessages_FoundStickerSets &result) {
searchSetsResultsDone(result);
}).fail([=] {
_searchSetsRequestId = 0;
if (!_searchCloudRequestId) {
toggleSearchLoading(false);
}
}).handleAllErrors().send();
}
void EmojiListWidget::cancelSearchRequest() {
toggleSearchLoading(false);
if (const auto requestId = base::take(_searchCloudRequestId)) {
_api.request(requestId).cancel();
}
if (const auto requestId = base::take(_searchSetsRequestId)) {
_api.request(requestId).cancel();
}
_searchRequestTimer.cancel();
_searchRequestQuery = QString();
_searchNextRequestQuery = QString();
_searchCloudCache.clear();
_searchSetsCache.clear();
_searchSets.clear();
}
void EmojiListWidget::searchCloudResultsDone(
const MTPmessages_FoundStickers &result) {
_searchCloudRequestId = 0;
result.match([&](const MTPDmessages_foundStickersNotModified &) {
LOG(("API: messages.foundStickersNotModified."));
}, [&](const MTPDmessages_foundStickers &data) {
auto it = _searchCloudCache.find(_searchRequestQuery);
if (it == _searchCloudCache.cend()) {
it = _searchCloudCache.emplace(
_searchRequestQuery,
std::vector<DocumentId>()).first;
}
for (const auto &sticker : data.vstickers().v) {
if (const auto doc = session().data().processDocument(
sticker)) {
it->second.push_back(doc->id);
}
}
if (!_searchSetsRequestId) {
toggleSearchLoading(false);
}
showSearchResults();
});
}
void EmojiListWidget::searchSetsResultsDone(
const MTPmessages_FoundStickerSets &result) {
_searchSetsRequestId = 0;
if (!_searchCloudRequestId) {
toggleSearchLoading(false);
}
result.match([&](const MTPDmessages_foundStickerSetsNotModified &) {
LOG(("API Error: "
"messages.foundStickerSetsNotModified not expected."));
}, [&](const MTPDmessages_foundStickerSets &data) {
auto it = _searchSetsCache.find(_searchRequestQuery);
if (it == _searchSetsCache.cend()) {
it = _searchSetsCache.emplace(
_searchRequestQuery,
std::vector<uint64>()).first;
}
for (const auto &setData : data.vsets().v) {
const auto set
= session().data().stickers().feedSet(setData);
if (set->stickers.empty() && set->covers.empty()) {
continue;
}
it->second.push_back(set->id);
}
showSearchResults();
});
}
void EmojiListWidget::showSearchResults() {
clearSelection();
_searchResults.clear();
_searchCustomIds.clear();
_searchSets.clear();
_searchEmoji.clear();
if (_mode == Mode::Full) {
auto plain = collectPlainSearchResults();
for (const auto emoji : plain) {
_searchResults.push_back({
.id = { emoji },
});
}
}
if (_mode != Mode::Full || session().premium()) {
appendPremiumSearchResults();
}
fillCloudSearchResults();
if (_mode == Mode::Full) {
appendLocalPackSearchResults();
}
fillCloudSearchSets();
resizeToWidth(width());
_recentShownCount = _searchResults.size();
update();
updateSelected();
}
void EmojiListWidget::fillCloudSearchResults() {
const auto it = _searchCloudCache.find(_searchRequestQuery);
if (it == _searchCloudCache.cend() || it->second.empty()) {
return;
}
const auto test = session().isTestMode();
for (const auto id : it->second) {
if (_searchResults.size() >= kCustomSearchLimit) {
break;
}
if (!_searchCustomIds.emplace(id).second) {
continue;
}
const auto document = session().data().document(id);
const auto sticker = document->sticker();
if (!sticker) {
continue;
}
const auto statusId = EmojiStatusId{ id };
_searchResults.push_back({
.custom = resolveCustomEmoji(
statusId,
document,
SearchEmojiSectionSetId()),
.id = { RecentEmojiDocument{ .id = id, .test = test } },
});
}
}
void EmojiListWidget::fillCloudSearchSets() {
const auto it = _searchSetsCache.find(_searchRequestQuery);
if (it == _searchSetsCache.cend() || it->second.empty()) {
return;
}
const auto &sets = session().data().stickers().sets();
const auto test = session().isTestMode();
for (const auto setId : it->second) {
const auto setIt = sets.find(setId);
if (setIt == sets.end()) {
continue;
}
const auto set = setIt->second.get();
const auto &list = set->stickers.empty()
? set->covers
: set->stickers;
if (list.empty()) {
continue;
}
auto customs = std::vector<CustomOne>();
customs.reserve(list.size());
for (const auto document : list) {
if (const auto sticker = document->sticker()) {
const auto statusId = EmojiStatusId{ document->id };
customs.push_back({
.custom = resolveCustomEmoji(
statusId,
document,
setId),
.document = document,
.emoji = Ui::Emoji::Find(sticker->alt),
});
}
}
if (customs.empty()) {
continue;
}
const auto installed = !!(set->flags
& Data::StickersSetFlag::Installed);
_searchSets.push_back({
.id = setId,
.set = set,
.thumbnailDocument = set->lookupThumbnailDocument(),
.title = set->title,
.list = std::move(customs),
.canRemove = installed,
});
}
}
EmojiListWidget::CustomSet &EmojiListWidget::searchSetBySection(
int section) {
Expects(section > 0 && section <= int(_searchSets.size()));
return _searchSets[section - 1];
}
const EmojiListWidget::CustomSet &EmojiListWidget::searchSetBySection(
int section) const {
Expects(section > 0 && section <= int(_searchSets.size()));
return _searchSets[section - 1];
}
void EmojiListWidget::provideRecent(
const std::vector<EmojiStatusId> &customRecentList) {
clearSelection();
@@ -791,6 +1167,18 @@ void EmojiListWidget::repaintCustom(uint64 setId) {
if (_searchMode) {
if (repaintSearch) {
update();
} else {
enumerateSections([&](const SectionInfo &info) {
if (info.section > 0
&& searchSetBySection(info.section).id == setId) {
update(
0,
info.rowsTop,
width(),
info.rowsBottom - info.rowsTop);
}
return true;
});
}
return;
}
@@ -906,6 +1294,16 @@ void EmojiListWidget::unloadCustomIn(const SectionInfo &info) {
}
}
return;
} else if (_searchMode && info.section > 0) {
auto &custom = searchSetBySection(info.section);
if (!custom.painted) {
return;
}
custom.painted = false;
for (const auto &single : custom.list) {
single.custom->unload();
}
return;
} else if (info.section < _staticCount) {
return;
}
@@ -989,7 +1387,22 @@ bool EmojiListWidget::enumerateSections(Callback callback) const {
if (_searchMode) {
info.section = i;
info.count = _searchResults.size();
return next();
if (!next()) {
return false;
}
++i;
for (auto &section : _searchSets) {
info.section = i++;
info.premiumRequired = section.premiumRequired;
info.count = int(section.list.size());
info.collapsed = !section.expanded
&& (!section.canRemove || section.premiumRequired)
&& (info.count > _columnCount * kCollapsedRows);
if (!next()) {
return false;
}
}
return true;
}
for (; i != _staticCount; ++i) {
info.section = i;
@@ -1041,7 +1454,9 @@ EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(
}
int EmojiListWidget::sectionsCount() const {
return _searchMode ? 1 : (_staticCount + int(_custom.size()));
return _searchMode
? (1 + int(_searchSets.size()))
: (_staticCount + int(_custom.size()));
}
void EmojiListWidget::setSingleSize(QSize size) {
@@ -1406,7 +1821,11 @@ void EmojiListWidget::paint(
auto selectedButton = std::get_if<OverButton>(!v::is_null(_pressed)
? &_pressed
: &_selected);
if (_searchResults.empty() && _searchMode) {
if (_searchResults.empty()
&& _searchSets.empty()
&& _searchMode
&& !_searchLoading
&& !_searchRequestTimer.isActive()) {
paintEmptySearchResults(p);
}
enumerateSections([&](const SectionInfo &info) {
@@ -1427,7 +1846,9 @@ void EmojiListWidget::paint(
if (info.section > 0 && clip.top() < info.rowsTop) {
p.setFont(st::emojiPanHeaderFont);
p.setPen(st().headerFg);
auto titleText = (info.section < _staticCount)
auto titleText = (_searchMode && info.section > 0)
? searchSetBySection(info.section).title
: (info.section < _staticCount)
? ChatHelpers::EmojiCategoryTitle(info.section)(tr::now)
: _custom[info.section - _staticCount].title;
auto titleWidth = st::emojiPanHeaderFont->width(titleText);
@@ -1508,8 +1929,15 @@ void EmojiListWidget::paint(
}
_overBg.paint(p, QRect(tl, st::emojiPanArea));
}
if (_searchMode) {
if (_searchMode && info.section == 0) {
drawRecent(p, context, w, _searchResults[index]);
} else if (_searchMode && info.section > 0) {
drawSearchSetCustom(
p,
context,
w,
info.section,
index);
} else if (info.section == int(Section::Recent)) {
drawRecent(p, context, w, _recent[index]);
} else if (info.section < _staticCount) {
@@ -1644,6 +2072,22 @@ void EmojiListWidget::drawCustom(
entry.custom->paint(p, *_emojiPaintContext);
}
void EmojiListWidget::drawSearchSetCustom(
QPainter &p,
const ExpandingContext &context,
QPoint position,
int section,
int index) {
auto &custom = searchSetBySection(section);
custom.painted = true;
auto &entry = custom.list[index];
_emojiPaintContext->scale = context.progress;
_emojiPaintContext->position = position
+ _innerPosition
+ _customPosition;
entry.custom->paint(p, *_emojiPaintContext);
}
bool EmojiListWidget::checkPickerHide() {
if (!_picker->isHidden() && !v::is_null(_pickerSelected)) {
_picker->hideAnimated();
@@ -1664,7 +2108,7 @@ EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
int index,
int section) const {
if (_searchMode) {
if (_searchMode && section == 0) {
if (index < _searchResults.size()) {
const auto document = std::get_if<RecentEmojiDocument>(
&_searchResults[index].id.data);
@@ -1673,6 +2117,13 @@ EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
}
}
return {};
} else if (_searchMode && section > 0) {
const auto &set = searchSetBySection(section);
if (index < int(set.list.size())) {
auto &entry = set.list[index];
return { entry.document, entry.collectible };
}
return {};
} else if (section == int(Section::Recent) && index < _recent.size()) {
const auto &recent = _recent[index];
if (recent.collectible) {
@@ -1698,11 +2149,15 @@ EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const {
const auto section = over ? over->section : -1;
const auto index = over ? over->index : -1;
return _searchMode
return (_searchMode && section == 0)
? ((index < _searchResults.size()
&& v::is<EmojiPtr>(_searchResults[index].id.data))
? v::get<EmojiPtr>(_searchResults[index].id.data)
: nullptr)
: (_searchMode && section > 0)
? ((index < int(searchSetBySection(section).list.size()))
? searchSetBySection(section).list[index].emoji
: nullptr)
: (section == int(Section::Recent)
&& index < _recent.size()
&& v::is<EmojiPtr>(_recent[index].id.data))
@@ -1824,10 +2279,13 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
if (const auto over = std::get_if<OverEmoji>(&_selected)) {
const auto section = over->section;
const auto index = over->index;
if (section >= _staticCount
&& sectionInfo(section).collapsed
if (sectionInfo(section).collapsed
&& index + 1 == _columnCount * kCollapsedRows) {
_custom[section - _staticCount].expanded = true;
if (_searchMode && section > 0) {
searchSetBySection(section).expanded = true;
} else if (section >= _staticCount) {
_custom[section - _staticCount].expanded = true;
}
resizeToWidth(width());
update();
return;
@@ -1840,13 +2298,20 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
selectCustom(lookupChosen(custom, over));
}
} else if (const auto set = std::get_if<OverSet>(&pressed)) {
Assert(set->section >= _staticCount
&& set->section < _staticCount + _custom.size());
displaySet(_custom[set->section - _staticCount].id);
const auto setId = (_searchMode && set->section > 0)
? searchSetBySection(set->section).id
: (set->section >= _staticCount)
? _custom[set->section - _staticCount].id
: uint64(0);
if (setId) {
displaySet(setId);
}
} else if (auto button = std::get_if<OverButton>(&pressed)) {
Assert(hasButton(button->section));
const auto id = hasColorButton(button->section)
? 0
: (_searchMode && button->section > 0)
? searchSetBySection(button->section).id
: _custom[button->section - _staticCount].id;
if (hasColorButton(button->section)) {
_pickerSelected = pressed;
@@ -2021,6 +2486,12 @@ QRect EmojiListWidget::colorButtonRect(const SectionInfo &info) const {
}
bool EmojiListWidget::hasRemoveButton(int index) const {
if (_searchMode) {
if (index > 0 && index <= int(_searchSets.size())) {
return searchSetBySection(index).canRemove;
}
return false;
}
if (index < _staticCount
|| index >= _staticCount + _custom.size()) {
return false;
@@ -2056,6 +2527,13 @@ QRect EmojiListWidget::removeButtonRect(const SectionInfo &info) const {
}
bool EmojiListWidget::hasAddButton(int index) const {
if (_searchMode) {
if (index > 0 && index <= int(_searchSets.size())) {
const auto &set = searchSetBySection(index);
return !set.canRemove && !set.premiumRequired;
}
return false;
}
if (index < _staticCount
|| index >= _staticCount + _custom.size()) {
return false;
@@ -2072,6 +2550,12 @@ QRect EmojiListWidget::addButtonRect(int index) const {
}
bool EmojiListWidget::hasUnlockButton(int index) const {
if (_searchMode) {
if (index > 0 && index <= int(_searchSets.size())) {
return searchSetBySection(index).premiumRequired;
}
return false;
}
if (index < _staticCount
|| index >= _staticCount + _custom.size()) {
return false;
@@ -2081,13 +2565,19 @@ bool EmojiListWidget::hasUnlockButton(int index) const {
}
QRect EmojiListWidget::unlockButtonRect(int index) const {
Expects(index >= _staticCount
&& index < _staticCount + _custom.size());
Expects((_searchMode
&& index > 0
&& index <= int(_searchSets.size()))
|| (index >= _staticCount
&& index < _staticCount + _custom.size()));
return buttonRect(sectionInfo(index), rightButton(index));
}
bool EmojiListWidget::hasButton(int index) const {
if (_searchMode) {
return (index > 0 && index <= int(_searchSets.size()));
}
if (hasColorButton(index)) {
return true;
} else if (index >= _staticCount
@@ -2121,6 +2611,15 @@ QRect EmojiListWidget::buttonRect(
}
auto EmojiListWidget::rightButton(int index) const -> const RightButton & {
if (_searchMode) {
Expects(index > 0 && index <= int(_searchSets.size()));
return hasAddButton(index)
? _add
: searchSetBySection(index).canRemove
? _restore
: _unlock;
}
Expects(index >= _staticCount
&& index < _staticCount + _custom.size());
@@ -2272,6 +2771,7 @@ void EmojiListWidget::processHideFinished() {
_picker->hideFast();
_pickerSelected = v::null;
}
cancelSearchRequest();
unloadAllCustom();
clearSelection();
}
@@ -2646,7 +3146,9 @@ int EmojiListWidget::paintButtonGetWidth(
if (!hasButton(info.section)) {
return 0;
}
auto &ripple = (info.section >= _staticCount)
auto &ripple = (_searchMode && info.section > 0)
? searchSetBySection(info.section).ripple
: (info.section >= _staticCount)
? _custom[info.section - _staticCount].ripple
: _colorAllRipple;
const auto colorAll = hasColorButton(info.section);
@@ -2801,9 +3303,13 @@ void EmojiListWidget::setSelected(OverState newSelected) {
void EmojiListWidget::setPressed(OverState newPressed) {
if (auto button = std::get_if<OverButton>(&_pressed)) {
Assert(hasColorButton(button->section)
|| (_searchMode && button->section > 0
&& button->section <= int(_searchSets.size()))
|| (button->section >= _staticCount
&& button->section < _staticCount + _custom.size()));
auto &ripple = (button->section >= _staticCount)
auto &ripple = (_searchMode && button->section > 0)
? searchSetBySection(button->section).ripple
: (button->section >= _staticCount)
? _custom[button->section - _staticCount].ripple
: _colorAllRipple;
if (ripple) {
@@ -2813,9 +3319,13 @@ void EmojiListWidget::setPressed(OverState newPressed) {
_pressed = newPressed;
if (auto button = std::get_if<OverButton>(&_pressed)) {
Assert(hasColorButton(button->section)
|| (_searchMode && button->section > 0
&& button->section <= int(_searchSets.size()))
|| (button->section >= _staticCount
&& button->section < _staticCount + _custom.size()));
auto &ripple = (button->section >= _staticCount)
auto &ripple = (_searchMode && button->section > 0)
? searchSetBySection(button->section).ripple
: (button->section >= _staticCount)
? _custom[button->section - _staticCount].ripple
: _colorAllRipple;
if (!ripple) {
@@ -2935,8 +3445,10 @@ uint64 EmojiListWidget::sectionSetId(int section) const {
|| section < _staticCount
|| (section - _staticCount) < _custom.size());
return _searchMode
return (_searchMode && section == 0)
? SearchEmojiSectionSetId()
: (_searchMode && section > 0)
? searchSetBySection(section).id
: (section < _staticCount)
? EmojiSectionSetId(static_cast<Section>(section))
: _custom[section - _staticCount].id;
@@ -299,6 +299,18 @@ private:
void setupSearch();
[[nodiscard]] std::vector<EmojiPtr> collectPlainSearchResults();
void appendPremiumSearchResults();
void appendLocalPackSearchResults();
void sendSearchRequest();
void sendSearchSetsRequest();
void cancelSearchRequest();
void toggleSearchLoading(bool loading);
void searchCloudResultsDone(const MTPmessages_FoundStickers &result);
void searchSetsResultsDone(const MTPmessages_FoundStickerSets &result);
void showSearchResults();
void fillCloudSearchResults();
void fillCloudSearchSets();
[[nodiscard]] CustomSet &searchSetBySection(int section);
[[nodiscard]] const CustomSet &searchSetBySection(int section) const;
void ensureLoaded(int section);
void updateSelected();
void setSelected(OverState newSelected);
@@ -346,6 +358,12 @@ private:
QPoint position,
int set,
int index);
void drawSearchSetCustom(
QPainter &p,
const ExpandingContext &context,
QPoint position,
int section,
int index);
void validateEmojiPaintContext(const ExpandingContext &context);
[[nodiscard]] bool hasColorButton(int index) const;
[[nodiscard]] QRect colorButtonRect(int index) const;
@@ -459,11 +477,21 @@ private:
rpl::event_stream<std::vector<QString>> _searchQueries;
std::vector<QString> _nextSearchQuery;
std::vector<QString> _searchQuery;
QString _searchQueryText;
base::flat_set<EmojiPtr> _searchEmoji;
base::flat_set<EmojiPtr> _searchEmojiPrevious;
base::flat_set<DocumentId> _searchCustomIds;
std::vector<RecentOne> _searchResults;
bool _searchMode = false;
std::map<QString, std::vector<DocumentId>> _searchCloudCache;
std::map<QString, std::vector<uint64>> _searchSetsCache;
std::vector<CustomSet> _searchSets;
QString _searchRequestQuery;
QString _searchNextRequestQuery;
QString _searchEmoticon;
mtpRequestId _searchCloudRequestId = 0;
mtpRequestId _searchSetsRequestId = 0;
bool _searchLoading = false;
int _rowsTop = 0;
int _rowsLeft = 0;
@@ -483,6 +511,7 @@ private:
OverState _pickerSelected;
QPoint _lastMousePos;
base::Timer _searchRequestTimer;
object_ptr<EmojiColorPicker> _picker;
base::Timer _showPickerTimer;
base::Timer _previewTimer;