From 95f5f289065117d43c0ed8dbea13da16eb3eec9e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Mar 2022 15:14:07 +0400 Subject: [PATCH] Add search to the Downloads section. --- .../downloads/info_downloads_inner_widget.cpp | 2 +- .../downloads/info_downloads_provider.cpp | 111 ++++++++++++++++-- .../info/downloads/info_downloads_provider.h | 15 +++ Telegram/SourceFiles/info/info_controller.cpp | 21 +++- Telegram/SourceFiles/info/info_controller.h | 2 + .../info/media/info_media_common.h | 2 + .../info/media/info_media_list_widget.cpp | 5 + .../info/media/info_media_provider.cpp | 4 + .../info/media/info_media_provider.h | 2 + 9 files changed, 148 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_inner_widget.cpp b/Telegram/SourceFiles/info/downloads/info_downloads_inner_widget.cpp index 38aba69a4d..f2c116fdfb 100644 --- a/Telegram/SourceFiles/info/downloads/info_downloads_inner_widget.cpp +++ b/Telegram/SourceFiles/info/downloads/info_downloads_inner_widget.cpp @@ -127,7 +127,7 @@ object_ptr InnerWidget::setupList() { result->lifetime()); _selectedLists.fire(result->selectedListValue()); _listTops.fire(result->topValue()); - _controller->mediaSourceQueryValue( + _controller->searchQueryValue( ) | rpl::start_with_next([this](const QString &query) { _empty->setSearchQuery(query); }, result->lifetime()); diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp index 8fcb3980e3..58a20eff8f 100644 --- a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp +++ b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/media/info_media_widget.h" #include "info/media/info_media_list_section.h" #include "info/info_controller.h" +#include "ui/text/format_song_document_name.h" #include "data/data_download_manager.h" #include "data/data_document.h" #include "data/data_media_types.h" @@ -70,7 +71,11 @@ bool Provider::isPossiblyMyItem(not_null item) { } std::optional Provider::fullCount() { - return _fullCount; + return _queryWords.empty() + ? _fullCount + : (_foundCount || _fullCount.has_value()) + ? _foundCount + : std::optional(); } void Provider::restart() { @@ -84,6 +89,27 @@ void Provider::checkPreload( bool preloadBottom) { } +void Provider::setSearchQuery(QString query) { + if (_query == query) { + return; + } + _query = query; + auto words = TextUtilities::PrepareSearchWords(_query); + if (!_started || _queryWords == words) { + return; + } + _queryWords = std::move(words); + if (searchMode()) { + _foundCount = 0; + for (auto &element : _elements) { + if ((element.found = computeIsFound(element))) { + ++_foundCount; + } + } + } + _refreshed.fire({}); +} + void Provider::refreshViewer() { if (_started) { return; @@ -99,7 +125,7 @@ void Provider::refreshViewer() { const auto item = id->object.item; if (!copy.remove(item) && !_downloaded.contains(item)) { _downloading.emplace(item); - _elements.push_back({ + addElementNow({ .item = item, .started = id->started, .path = id->path, @@ -180,21 +206,37 @@ void Provider::performAdd() { for (auto &element : base::take(_addPostponed)) { _downloaded.emplace(element.item); if (!_downloading.remove(element.item)) { - _elements.push_back(std::move(element)); + addElementNow(std::move(element)); } } refreshPostponed(true); } +void Provider::addElementNow(Element &&element) { + _elements.push_back(std::move(element)); + auto &added = _elements.back(); + fillSearchIndex(added); + added.found = searchMode() && computeIsFound(added); + if (added.found) { + ++_foundCount; + } +} + void Provider::remove(not_null item) { _addPostponed.erase( ranges::remove(_addPostponed, item, &Element::item), end(_addPostponed)); _downloading.remove(item); _downloaded.remove(item); - _elements.erase( - ranges::remove(_elements, item, &Element::item), - end(_elements)); + const auto proj = [&](const Element &element) { + if (element.item != item) { + return false; + } else if (element.found && searchMode()) { + --_foundCount; + } + return true; + }; + _elements.erase(ranges::remove_if(_elements, proj), end(_elements)); if (const auto i = _layouts.find(item); i != end(_layouts)) { _layoutRemoved.fire(i->second.item.get()); _layouts.erase(i); @@ -252,10 +294,14 @@ rpl::producer<> Provider::refreshed() { std::vector Provider::fillSections( not_null delegate) { - markLayoutsStale(); + const auto search = searchMode(); + + if (!search) { + markLayoutsStale(); + } const auto guard = gsl::finally([&] { clearStaleLayouts(); }); - if (_elements.empty()) { + if (_elements.empty() || (search && !_foundCount)) { return {}; } @@ -264,7 +310,9 @@ std::vector Provider::fillSections( ListSection(Type::File, sectionDelegate())); auto §ion = result.back(); for (const auto &element : ranges::views::reverse(_elements)) { - if (auto layout = getLayout(element, delegate)) { + if (search && !element.found) { + continue; + } else if (auto layout = getLayout(element, delegate)) { section.addItem(layout); } } @@ -316,6 +364,47 @@ bool Provider::isAfter( return false; } +bool Provider::searchMode() const { + return !_queryWords.empty(); +} + +void Provider::fillSearchIndex(Element &element) { + auto strings = QStringList(QFileInfo(element.path).fileName()); + if (const auto media = element.item->media()) { + if (const auto document = media->document()) { + strings.append(document->filename()); + strings.append(Ui::Text::FormatDownloadsName(document).text); + } + } + element.words = TextUtilities::PrepareSearchWords(strings.join(' ')); + element.letters.clear(); + for (const auto &word : element.words) { + element.letters.emplace(word.front()); + } +} + +bool Provider::computeIsFound(const Element &element) const { + Expects(!_queryWords.empty()); + + const auto has = [&](const QString &queryWord) { + if (!element.letters.contains(queryWord.front())) { + return false; + } + for (const auto &word : element.words) { + if (word.startsWith(queryWord)) { + return true; + } + } + return false; + }; + for (const auto &queryWord : _queryWords) { + if (!has(queryWord)) { + return false; + } + } + return true; +} + void Provider::itemRemoved(not_null item) { remove(item); } @@ -393,9 +482,13 @@ void Provider::applyDragSelection( selected.clear(); return; } + const auto search = !_queryWords.isEmpty(); auto chosen = base::flat_set>(); chosen.reserve(till - from); for (auto i = from; i != till; ++i) { + if (search && !i->found) { + continue; + } const auto item = i->item; chosen.emplace(item); ChangeItemSelection( diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_provider.h b/Telegram/SourceFiles/info/downloads/info_downloads_provider.h index 51b3c38ede..08f507e888 100644 --- a/Telegram/SourceFiles/info/downloads/info_downloads_provider.h +++ b/Telegram/SourceFiles/info/downloads/info_downloads_provider.h @@ -44,6 +44,8 @@ public: void refreshViewer() override; rpl::producer<> refreshed() override; + void setSearchQuery(QString query); + std::vector fillSections( not_null delegate) override; rpl::producer> layoutRemoved() override; @@ -86,6 +88,10 @@ private: not_null item; int64 started = 0; // unixtime * 1000 QString path; + + QStringList words; + base::flat_set letters; + bool found = false; }; bool sectionHasFloatingHeader() override; @@ -94,6 +100,10 @@ private: not_null item, not_null previous) override; + [[nodiscard]] bool searchMode() const; + void fillSearchIndex(Element &element); + [[nodiscard]] bool computeIsFound(const Element &element) const; + void itemRemoved(not_null item); void markLayoutsStale(); void clearStaleLayouts(); @@ -102,6 +112,7 @@ private: void addPostponed(not_null entry); void performRefresh(); void performAdd(); + void addElementNow(Element &&element); void remove(not_null item); void trackItemSession(not_null item); @@ -127,6 +138,10 @@ private: rpl::event_stream> _layoutRemoved; rpl::event_stream<> _refreshed; + QString _query; + QStringList _queryWords; + int _foundCount = 0; + base::flat_map, rpl::lifetime> _trackedSessions; bool _postponedRefreshSort = false; bool _postponedRefresh = false; diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index d9280a32e5..799ca59e02 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -108,6 +108,10 @@ rpl::producer AbstractController::mediaSourceQueryValue() const { return rpl::single(QString()); } +rpl::producer AbstractController::searchQueryValue() const { + return rpl::single(QString()); +} + AbstractController::AbstractController( not_null parent) : SessionNavigation(&parent->session()) @@ -221,8 +225,8 @@ void Controller::updateSearchControllers( : Section::MediaType::kCount; const auto hasMediaSearch = isMedia && SharedMediaAllowSearch(mediaType); - const auto hasCommonGroupsSearch - = (type == Type::CommonGroups); + const auto hasCommonGroupsSearch = (type == Type::CommonGroups); + const auto hasDownloadsSearch = (type == Type::Downloads); const auto hasMembersSearch = (type == Type::Members) || (type == Type::Profile); const auto searchQuery = memento->searchFieldQuery(); @@ -236,7 +240,10 @@ void Controller::updateSearchControllers( } else { _searchController = nullptr; } - if (hasMediaSearch || hasCommonGroupsSearch || hasMembersSearch) { + if (hasMediaSearch + || hasCommonGroupsSearch + || hasDownloadsSearch + || hasMembersSearch) { _searchFieldController = std::make_unique( searchQuery); @@ -300,9 +307,11 @@ rpl::producer Controller::searchEnabledByContent() const { } rpl::producer Controller::mediaSourceQueryValue() const { - return _searchController - ? _searchController->currentQueryValue() - : rpl::single(QString()); // #TODO downloads search + return _searchController->currentQueryValue(); +} + +rpl::producer Controller::searchQueryValue() const { + return searchFieldController()->queryValue(); } rpl::producer Controller::mediaSource( diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index 1656a5c78b..3f0294fb85 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -141,6 +141,7 @@ public: int limitBefore, int limitAfter) const; virtual rpl::producer mediaSourceQueryValue() const; + virtual rpl::producer searchQueryValue() const; void showSection( std::shared_ptr memento, @@ -198,6 +199,7 @@ public: int limitBefore, int limitAfter) const override; rpl::producer mediaSourceQueryValue() const override; + rpl::producer searchQueryValue() const override; bool takeSearchStartsFocused() { return base::take(_searchStartsFocused); } diff --git a/Telegram/SourceFiles/info/media/info_media_common.h b/Telegram/SourceFiles/info/media/info_media_common.h index 0bb2b55757..20c3051ca5 100644 --- a/Telegram/SourceFiles/info/media/info_media_common.h +++ b/Telegram/SourceFiles/info/media/info_media_common.h @@ -158,6 +158,8 @@ public: not_null item, not_null document) = 0; + virtual void setSearchQuery(QString query) = 0; + [[nodiscard]] virtual int64 scrollTopStatePosition( not_null item) = 0; [[nodiscard]] virtual HistoryItem *scrollTopStateItem( diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 6317b2b810..82170fd9e6 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -162,6 +162,11 @@ void ListWidget::start() { if (_controller->isDownloads()) { _provider->refreshViewer(); + + _controller->searchQueryValue( + ) | rpl::start_with_next([this](QString &&query) { + _provider->setSearchQuery(std::move(query)); + }, lifetime()); } else { trackSession(&session()); diff --git a/Telegram/SourceFiles/info/media/info_media_provider.cpp b/Telegram/SourceFiles/info/media/info_media_provider.cpp index 2d1896ae04..4cf51b462f 100644 --- a/Telegram/SourceFiles/info/media/info_media_provider.cpp +++ b/Telegram/SourceFiles/info/media/info_media_provider.cpp @@ -341,6 +341,10 @@ bool Provider::isAfter( return (GetUniversalId(a) < GetUniversalId(b)); } +void Provider::setSearchQuery(QString query) { + Unexpected("Media::Provider::setSearchQuery."); +} + SparseIdsMergedSlice::Key Provider::sliceKey( UniversalMsgId universalId) const { using Key = SparseIdsMergedSlice::Key; diff --git a/Telegram/SourceFiles/info/media/info_media_provider.h b/Telegram/SourceFiles/info/media/info_media_provider.h index d89c74c700..69b272f8b9 100644 --- a/Telegram/SourceFiles/info/media/info_media_provider.h +++ b/Telegram/SourceFiles/info/media/info_media_provider.h @@ -46,6 +46,8 @@ public: not_null a, not_null b) override; + void setSearchQuery(QString query) override; + ListItemSelectionData computeSelectionData( not_null item, TextSelection selection) override;