From c48ac28204b49ccd4f12e42928640a0a500321b4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Jan 2023 18:46:16 +0400 Subject: [PATCH] Search stickers by emoji. --- Telegram/SourceFiles/apiwrap.cpp | 10 +- Telegram/SourceFiles/apiwrap.h | 6 +- .../SourceFiles/boxes/sticker_set_box.cpp | 5 +- .../chat_helpers/field_autocomplete.cpp | 6 +- .../chat_helpers/stickers_list_widget.cpp | 41 ++++- .../chat_helpers/stickers_list_widget.h | 4 +- .../data/stickers/data_stickers.cpp | 153 +++++++++++------- .../SourceFiles/data/stickers/data_stickers.h | 7 +- .../data/stickers/data_stickers_set.cpp | 2 + .../data/stickers/data_stickers_set.h | 4 +- .../SourceFiles/storage/storage_account.cpp | 8 +- 11 files changed, 161 insertions(+), 85 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 5dea3d898..29bcf9dd6 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2543,8 +2543,8 @@ void ApiWrap::setGroupStickerSet( } std::vector> *ApiWrap::stickersByEmoji( - not_null emoji) { - const auto it = _stickersByEmoji.find(emoji); + const QString &key) { + const auto it = _stickersByEmoji.find(key); const auto sendRequest = [&] { if (it == _stickersByEmoji.end()) { return true; @@ -2559,7 +2559,7 @@ std::vector> *ApiWrap::stickersByEmoji( ? it->second.hash : uint64(0); request(MTPmessages_GetStickers( - MTP_string(emoji->text()), + MTP_string(key), MTP_long(hash) )).done([=](const MTPmessages_Stickers &result) { if (result.type() == mtpc_messages_stickersNotModified) { @@ -2567,7 +2567,7 @@ std::vector> *ApiWrap::stickersByEmoji( } Assert(result.type() == mtpc_messages_stickers); const auto &data = result.c_messages_stickers(); - auto &entry = _stickersByEmoji[emoji]; + auto &entry = _stickersByEmoji[key]; entry.list.clear(); entry.list.reserve(data.vstickers().v.size()); for (const auto &sticker : data.vstickers().v) { @@ -2584,7 +2584,7 @@ std::vector> *ApiWrap::stickersByEmoji( }).send(); } if (it == _stickersByEmoji.end()) { - _stickersByEmoji.emplace(emoji, StickersByEmoji()); + _stickersByEmoji.emplace(key, StickersByEmoji()); } else if (it->second.received > 0) { return &it->second.list; } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 349425338..1ae6ab288 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -238,8 +238,8 @@ public: void setGroupStickerSet( not_null megagroup, const StickerSetIdentifier &set); - std::vector> *stickersByEmoji( - not_null emoji); + [[nodiscard]] std::vector> *stickersByEmoji( + const QString &key); void joinChannel(not_null channel); void leaveChannel(not_null channel); @@ -596,7 +596,7 @@ private: base::Timer _featuredSetsReadTimer; base::flat_set _featuredSetsRead; - base::flat_map, StickersByEmoji> _stickersByEmoji; + base::flat_map _stickersByEmoji; mtpRequestId _contactsRequestId = 0; mtpRequestId _contactsStatusesRequestId = 0; diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index ff62368da..ba89371fb 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -70,7 +70,6 @@ constexpr auto kGrayLockOpacity = 0.3; using Data::StickersSet; using Data::StickersPack; -using Data::StickersByEmojiMap; using SetFlag = Data::StickersSetFlag; [[nodiscard]] std::optional ComputeImageColor(const QImage &frame) { @@ -337,7 +336,7 @@ private: bool _repaintScheduled = false; StickersPack _pack; - StickersByEmojiMap _emoji; + base::flat_map _emoji; bool _loaded = false; uint64 _setId = 0; uint64 _setAccessHash = 0; @@ -729,7 +728,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { p.push_back(doc); } - _emoji.insert(original, p); + _emoji[original] = std::move(p); } }); } diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 0cb9d164d..2156fa805 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -365,10 +365,8 @@ inline int indexOfInFirstN(const T &v, const U &elem, int last) { } FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() { - const auto list = _controller->session().data().stickers().getListByEmoji( - _emoji, - _stickersSeed - ); + const auto data = &_controller->session().data().stickers(); + const auto list = data->getListByEmoji({ _emoji }, _stickersSeed); auto result = ranges::views::all( list ) | ranges::views::transform([](not_null sticker) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 2fe2e53b4..49a97cc5e 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -532,13 +532,19 @@ void StickersListWidget::sendSearchRequest() { }).handleAllErrors().send(); } -void StickersListWidget::searchForSets(const QString &query) { +void StickersListWidget::searchForSets( + const QString &query, + std::vector emoji) { const auto cleaned = query.trimmed(); if (cleaned.isEmpty()) { cancelSetsSearch(); return; } + _filteredStickers = session().data().stickers().getListByEmoji( + std::move(emoji), + 0, + true); if (_searchQuery != cleaned) { _search->setLoading(false); if (const auto requestId = base::take(_searchRequestId)) { @@ -562,6 +568,7 @@ void StickersListWidget::cancelSetsSearch() { } _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery = QString(); + _filteredStickers.clear(); _searchCache.clear(); refreshSearchRows(nullptr); } @@ -591,6 +598,7 @@ void StickersListWidget::refreshSearchRows( } }); + fillFilteredStickersRow(); fillLocalSearchRows(_searchNextQuery); if (!cloudSets && _searchNextQuery.isEmpty()) { @@ -657,6 +665,27 @@ void StickersListWidget::fillCloudSearchRows( } } +void StickersListWidget::fillFilteredStickersRow() { + if (_filteredStickers.empty()) { + return; + } + auto elements = ranges::views::all( + _filteredStickers + ) | ranges::views::transform([](not_null document) { + return Sticker{ document }; + }) | ranges::to_vector; + + _searchSets.emplace_back( + SearchEmojiSectionSetId(), + nullptr, + Data::StickersSetFlag::Special, + QString(), // title + QString(), // shortName + _filteredStickers.size(), + false, // externalLayout + std::move(elements)); +} + void StickersListWidget::addSearchRow(not_null set) { const auto skipPremium = !session().premiumPossible(); auto elements = PrepareStickers( @@ -2528,11 +2557,17 @@ void StickersListWidget::beforeHiding() { void StickersListWidget::setupSearch() { const auto session = &_controller->session(); _search = MakeSearch(this, st(), [=](std::vector &&query) { - searchForSets(ranges::accumulate(query, QString(), []( + auto emoji = query | ranges::views::transform([](const QString &k) { + return Ui::Emoji::Find(k); + }) | ranges::views::filter([](EmojiPtr emoji) { + return (emoji != nullptr); + }) | ranges::to_vector; + auto text = ranges::accumulate(query, QString(), []( QString a, QString b) { return a.isEmpty() ? b : (a + ' ' + b); - })); + }); + searchForSets(std::move(text), std::move(emoji)); }, session); } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index f3769aa25..d89ed8c5d 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -89,7 +89,7 @@ public: uint64 currentSet(int yOffset) const; void sendSearchRequest(); - void searchForSets(const QString &query); + void searchForSets(const QString &query, std::vector emoji); std::shared_ptr getLottieRenderer(); @@ -319,6 +319,7 @@ private: void searchResultsDone(const MTPmessages_FoundStickerSets &result); void refreshSearchRows(); void refreshSearchRows(const std::vector *cloudSets); + void fillFilteredStickersRow(); void fillLocalSearchRows(const QString &query); void fillCloudSearchRows(const std::vector &cloudSets); void addSearchRow(not_null set); @@ -387,6 +388,7 @@ private: std::unique_ptr _premiumMark; + std::vector> _filteredStickers; std::map> _searchCache; std::vector> _searchIndex; base::Timer _searchRequestTimer; diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.cpp b/Telegram/SourceFiles/data/stickers/data_stickers.cpp index d78176c59..80d6ab959 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp @@ -120,10 +120,10 @@ void RemoveFromSet( set->dates.erase(set->dates.begin() + index); } for (auto i = set->emoji.begin(); i != set->emoji.end();) { - const auto index = i->indexOf(document); + const auto index = i->second.indexOf(document); if (index >= 0) { - i->removeAt(index); - if (i->empty()) { + i->second.removeAt(index); + if (i->second.empty()) { i = set->emoji.erase(i); continue; } @@ -236,10 +236,10 @@ void Stickers::incrementSticker(not_null document) { } set->stickers.removeAt(index); for (auto i = set->emoji.begin(); i != set->emoji.end();) { - if (const auto index = i->indexOf(document); index >= 0) { - removedFromEmoji.emplace_back(i.key()); - i->removeAt(index); - if (i->isEmpty()) { + if (const auto index = i->second.indexOf(document); index >= 0) { + removedFromEmoji.emplace_back(i->first); + i->second.removeAt(index); + if (i->second.empty()) { i = set->emoji.erase(i); continue; } @@ -527,10 +527,10 @@ void Stickers::checkFavedLimit( auto removing = set.stickers.back(); set.stickers.pop_back(); for (auto i = set.emoji.begin(); i != set.emoji.end();) { - auto index = i->indexOf(removing); + auto index = i->second.indexOf(removing); if (index >= 0) { - i->removeAt(index); - if (i->empty()) { + i->second.removeAt(index); + if (i->second.empty()) { i = set.emoji.erase(i); continue; } @@ -563,7 +563,7 @@ void Stickers::moveFavedToFront(StickersSet &set, int index) { set.stickers[index + 1] = set.stickers[index]; } set.stickers[0] = document; - for (auto &list : set.emoji) { + for (auto &[emoji, list] : set.emoji) { auto index = list.indexOf(document); if (index > 0) { while (index-- != 0) { @@ -816,7 +816,7 @@ void Stickers::setPackAndEmoji( p.push_back(document); } - set.emoji.insert(emoji, p); + set.emoji[emoji] = std::move(p); } } } @@ -1042,7 +1042,7 @@ void Stickers::featuredReceived( set->flags = flags | (set->flags & (SetFlag::NotLoaded | SetFlag::Special)); set->installDate = installDate; - if (set->count != data->vcount().v || set->hash != data->vhash().v || set->emoji.isEmpty()) { + if (set->count != data->vcount().v || set->hash != data->vhash().v || set->emoji.empty()) { set->count = data->vcount().v; set->hash = data->vhash().v; set->flags |= SetFlag::NotLoaded; // need to request this set @@ -1134,9 +1134,14 @@ void Stickers::gifsReceived(const QVector &items, uint64 hash) { } std::vector> Stickers::getListByEmoji( - not_null emoji, - uint64 seed) { - const auto original = emoji->original(); + std::vector emoji, + uint64 seed, + bool forceAllResults) { + auto all = base::flat_set(); + for (const auto &one : emoji) { + all.emplace(one->original()); + } + const auto single = (all.size() == 1) ? all.front() : nullptr; struct StickerWithDate { not_null document; @@ -1187,7 +1192,7 @@ std::vector> Stickers::getListByEmoji( ? date : date / 2; }; - const auto InstallDate = [&](not_null document) { + const auto RecentInstallDate = [&](not_null document) { Expects(document->sticker() != nullptr); const auto sticker = document->sticker(); @@ -1203,24 +1208,37 @@ std::vector> Stickers::getListByEmoji( auto recentIt = sets.find(Stickers::CloudRecentSetId); if (recentIt != sets.cend()) { const auto recent = recentIt->second.get(); - auto i = recent->emoji.constFind(original); - if (i != recent->emoji.cend()) { - result.reserve(i->size()); - for (const auto document : *i) { - const auto usageDate = [&] { - if (recent->dates.empty()) { - return TimeId(0); + const auto i = single + ? recent->emoji.find(single) + : recent->emoji.end(); + const auto list = (i != recent->emoji.end()) + ? &i->second + : !single + ? &recent->stickers + : nullptr; + if (list) { + const auto count = int(list->size()); + result.reserve(count); + for (auto i = 0; i != count; ++i) { + const auto document = (*list)[i]; + const auto sticker = document->sticker(); + auto index = i; + if (!sticker) { + continue; + } else if (!single) { + const auto main = Ui::Emoji::Find(sticker->alt); + if (!main || !all.contains(main)) { + continue; } - const auto index = recent->stickers.indexOf(document); - if (index < 0) { - return TimeId(0); - } - Assert(index < recent->dates.size()); - return recent->dates[index]; - }(); + } else { + index = recent->stickers.indexOf(document); + } + const auto usageDate = (recent->dates.empty() || index < 0) + ? 0 + : recent->dates[index]; const auto date = usageDate ? usageDate - : InstallDate(document); + : RecentInstallDate(document); result.push_back({ document, date ? date : CreateRecentSortKey(document) }); @@ -1236,25 +1254,40 @@ std::vector> Stickers::getListByEmoji( continue; } const auto set = it->second.get(); - if (set->emoji.isEmpty()) { + if (set->emoji.empty()) { setsToRequest.emplace(set->id, set->accessHash); set->flags |= SetFlag::NotLoaded; continue; } - auto i = set->emoji.constFind(original); - if (i == set->emoji.cend()) { - continue; - } const auto my = (set->flags & SetFlag::Installed); - result.reserve(result.size() + i->size()); - for (const auto document : *i) { - const auto installDate = my ? set->installDate : TimeId(0); - const auto date = (installDate > 1) - ? InstallDateAdjusted(installDate, document) - : my - ? CreateMySortKey(document) - : CreateFeaturedSortKey(document); - add(document, date); + const auto i = single + ? set->emoji.find(single) + : set->emoji.end(); + const auto list = (i != set->emoji.end()) + ? &i->second + : !single + ? &set->stickers + : nullptr; + if (list) { + result.reserve(result.size() + list->size()); + for (const auto document : *list) { + const auto sticker = document->sticker(); + if (!sticker) { + return; + } else if (!single) { + const auto main = Ui::Emoji::Find(sticker->alt); + if (!main || !all.contains(main)) { + return; + } + } + const auto installDate = my ? set->installDate : TimeId(0); + const auto date = (installDate > 1) + ? InstallDateAdjusted(installDate, document) + : my + ? CreateMySortKey(document) + : CreateFeaturedSortKey(document); + add(document, date); + } } } }; @@ -1269,15 +1302,21 @@ std::vector> Stickers::getListByEmoji( session().api().requestStickerSets(); } - if (Core::App().settings().suggestStickersByEmoji()) { - const auto others = session().api().stickersByEmoji(original); - if (!others) { + if (forceAllResults || Core::App().settings().suggestStickersByEmoji()) { + const auto key = ranges::accumulate( + all, + QString(), + ranges::plus(), + &Ui::Emoji::One::text); + const auto others = session().api().stickersByEmoji(key); + if (others) { + result.reserve(result.size() + others->size()); + for (const auto document : *others) { + add(document, CreateOtherSortKey(document)); + } + } else if (!forceAllResults) { return {}; } - result.reserve(result.size() + others->size()); - for (const auto document : *others) { - add(document, CreateOtherSortKey(document)); - } } ranges::sort(result, std::greater<>(), &StickerWithDate::date); @@ -1360,8 +1399,8 @@ std::optional>> Stickers::getEmojiListFromSet( const auto set = it->second.get(); auto result = std::vector>(); for (auto i = set->emoji.cbegin(), e = set->emoji.cend(); i != e; ++i) { - if (i->contains(document)) { - result.emplace_back(i.key()); + if (i->second.contains(document)) { + result.emplace_back(i->first); } } if (result.empty()) { @@ -1422,7 +1461,7 @@ not_null Stickers::feedSet(const MTPStickerSet &info) { : TimeId(0); if (set->count != data.vcount().v || set->hash != data.vhash().v - || set->emoji.isEmpty()) { + || set->emoji.empty()) { // Need to request this data. set->count = data.vcount().v; set->hash = data.vhash().v; @@ -1544,7 +1583,7 @@ void Stickers::feedSetStickers( } p.push_back(document); } - set->emoji.insert(emoji, p); + set->emoji[emoji] = std::move(p); } } diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.h b/Telegram/SourceFiles/data/stickers/data_stickers.h index 502b8b79d..da202f337 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers.h @@ -48,7 +48,7 @@ public: static constexpr auto RecentSetId = 0xFFFFFFFFFFFFFFFEULL; static constexpr auto NoneSetId = 0xFFFFFFFFFFFFFFFDULL; static constexpr auto FeaturedSetId = 0xFFFFFFFFFFFFFFFBULL; - + // For cloud-stored recent stickers. static constexpr auto CloudRecentSetId = 0xFFFFFFFFFFFFFFFCULL; static constexpr auto CloudRecentAttachedSetId = 0xFFFFFFFFFFFFFFF9ULL; @@ -230,8 +230,9 @@ public: void gifsReceived(const QVector &items, uint64 hash); std::vector> getListByEmoji( - not_null emoji, - uint64 seed); + std::vector emoji, + uint64 seed, + bool forceAllResults = false); std::optional>> getEmojiListFromSet( not_null document); diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp index daaaf89ac..f9db8da02 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp @@ -77,6 +77,8 @@ StickersSet::StickersSet( , _owner(owner) { } +StickersSet::~StickersSet() = default; + Data::Session &StickersSet::owner() const { return *_owner; } diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.h b/Telegram/SourceFiles/data/stickers/data_stickers_set.h index 4fc9b5f82..4fb30b3d1 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.h @@ -22,7 +22,6 @@ class Session; using StickersSetsOrder = QList; using SavedGifs = QVector; using StickersPack = QVector; -using StickersByEmojiMap = QMap; enum class StickersType : uchar; @@ -77,6 +76,7 @@ public: int count, StickersSetFlags flags, TimeId installDate); + ~StickersSet(); [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; @@ -111,7 +111,7 @@ public: StickersPack covers; StickersPack stickers; std::vector dates; - StickersByEmojiMap emoji; + base::flat_map emoji; private: const not_null _owner; diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 429881119..5cb5ac046 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -1656,8 +1656,8 @@ void Account::writeStickerSet( } stream << qint32(set.emoji.size()); for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { - stream << j.key()->id() << qint32(j->size()); - for (const auto sticker : *j) { + stream << j->first->id() << qint32(j->second.size()); + for (const auto sticker : j->second) { stream << quint64(sticker->id); } } @@ -1724,7 +1724,7 @@ void Account::writeStickerSets( size += sizeof(qint32); // emojiCount for (auto j = raw->emoji.cbegin(), e = raw->emoji.cend(); j != e; ++j) { - size += Serialize::stringSize(j.key()->id()) + sizeof(qint32) + (j->size() * sizeof(quint64)); + size += Serialize::stringSize(j->first->id()) + sizeof(qint32) + (j->second.size() * sizeof(quint64)); } ++setsCount; @@ -1960,7 +1960,7 @@ void Account::readStickerSets( if (fillStickers) { if (auto emoji = Ui::Emoji::Find(emojiString)) { emoji = emoji->original(); - set->emoji.insert(emoji, pack); + set->emoji[emoji] = std::move(pack); } } }