Add search to the Downloads section.

This commit is contained in:
John Preston 2022-03-11 15:14:07 +04:00
parent 5be72e8ce2
commit 95f5f28906
9 changed files with 148 additions and 16 deletions

View file

@ -127,7 +127,7 @@ object_ptr<Media::ListWidget> InnerWidget::setupList() {
result->lifetime()); result->lifetime());
_selectedLists.fire(result->selectedListValue()); _selectedLists.fire(result->selectedListValue());
_listTops.fire(result->topValue()); _listTops.fire(result->topValue());
_controller->mediaSourceQueryValue( _controller->searchQueryValue(
) | rpl::start_with_next([this](const QString &query) { ) | rpl::start_with_next([this](const QString &query) {
_empty->setSearchQuery(query); _empty->setSearchQuery(query);
}, result->lifetime()); }, result->lifetime());

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/media/info_media_widget.h" #include "info/media/info_media_widget.h"
#include "info/media/info_media_list_section.h" #include "info/media/info_media_list_section.h"
#include "info/info_controller.h" #include "info/info_controller.h"
#include "ui/text/format_song_document_name.h"
#include "data/data_download_manager.h" #include "data/data_download_manager.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
@ -70,7 +71,11 @@ bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
} }
std::optional<int> Provider::fullCount() { std::optional<int> Provider::fullCount() {
return _fullCount; return _queryWords.empty()
? _fullCount
: (_foundCount || _fullCount.has_value())
? _foundCount
: std::optional<int>();
} }
void Provider::restart() { void Provider::restart() {
@ -84,6 +89,27 @@ void Provider::checkPreload(
bool preloadBottom) { 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() { void Provider::refreshViewer() {
if (_started) { if (_started) {
return; return;
@ -99,7 +125,7 @@ void Provider::refreshViewer() {
const auto item = id->object.item; const auto item = id->object.item;
if (!copy.remove(item) && !_downloaded.contains(item)) { if (!copy.remove(item) && !_downloaded.contains(item)) {
_downloading.emplace(item); _downloading.emplace(item);
_elements.push_back({ addElementNow({
.item = item, .item = item,
.started = id->started, .started = id->started,
.path = id->path, .path = id->path,
@ -180,21 +206,37 @@ void Provider::performAdd() {
for (auto &element : base::take(_addPostponed)) { for (auto &element : base::take(_addPostponed)) {
_downloaded.emplace(element.item); _downloaded.emplace(element.item);
if (!_downloading.remove(element.item)) { if (!_downloading.remove(element.item)) {
_elements.push_back(std::move(element)); addElementNow(std::move(element));
} }
} }
refreshPostponed(true); 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<const HistoryItem*> item) { void Provider::remove(not_null<const HistoryItem*> item) {
_addPostponed.erase( _addPostponed.erase(
ranges::remove(_addPostponed, item, &Element::item), ranges::remove(_addPostponed, item, &Element::item),
end(_addPostponed)); end(_addPostponed));
_downloading.remove(item); _downloading.remove(item);
_downloaded.remove(item); _downloaded.remove(item);
_elements.erase( const auto proj = [&](const Element &element) {
ranges::remove(_elements, item, &Element::item), if (element.item != item) {
end(_elements)); 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)) { if (const auto i = _layouts.find(item); i != end(_layouts)) {
_layoutRemoved.fire(i->second.item.get()); _layoutRemoved.fire(i->second.item.get());
_layouts.erase(i); _layouts.erase(i);
@ -252,10 +294,14 @@ rpl::producer<> Provider::refreshed() {
std::vector<ListSection> Provider::fillSections( std::vector<ListSection> Provider::fillSections(
not_null<Overview::Layout::Delegate*> delegate) { not_null<Overview::Layout::Delegate*> delegate) {
const auto search = searchMode();
if (!search) {
markLayoutsStale(); markLayoutsStale();
}
const auto guard = gsl::finally([&] { clearStaleLayouts(); }); const auto guard = gsl::finally([&] { clearStaleLayouts(); });
if (_elements.empty()) { if (_elements.empty() || (search && !_foundCount)) {
return {}; return {};
} }
@ -264,7 +310,9 @@ std::vector<ListSection> Provider::fillSections(
ListSection(Type::File, sectionDelegate())); ListSection(Type::File, sectionDelegate()));
auto &section = result.back(); auto &section = result.back();
for (const auto &element : ranges::views::reverse(_elements)) { 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); section.addItem(layout);
} }
} }
@ -316,6 +364,47 @@ bool Provider::isAfter(
return false; 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<const HistoryItem*> item) { void Provider::itemRemoved(not_null<const HistoryItem*> item) {
remove(item); remove(item);
} }
@ -393,9 +482,13 @@ void Provider::applyDragSelection(
selected.clear(); selected.clear();
return; return;
} }
const auto search = !_queryWords.isEmpty();
auto chosen = base::flat_set<not_null<const HistoryItem*>>(); auto chosen = base::flat_set<not_null<const HistoryItem*>>();
chosen.reserve(till - from); chosen.reserve(till - from);
for (auto i = from; i != till; ++i) { for (auto i = from; i != till; ++i) {
if (search && !i->found) {
continue;
}
const auto item = i->item; const auto item = i->item;
chosen.emplace(item); chosen.emplace(item);
ChangeItemSelection( ChangeItemSelection(

View file

@ -44,6 +44,8 @@ public:
void refreshViewer() override; void refreshViewer() override;
rpl::producer<> refreshed() override; rpl::producer<> refreshed() override;
void setSearchQuery(QString query);
std::vector<Media::ListSection> fillSections( std::vector<Media::ListSection> fillSections(
not_null<Overview::Layout::Delegate*> delegate) override; not_null<Overview::Layout::Delegate*> delegate) override;
rpl::producer<not_null<Media::BaseLayout*>> layoutRemoved() override; rpl::producer<not_null<Media::BaseLayout*>> layoutRemoved() override;
@ -86,6 +88,10 @@ private:
not_null<HistoryItem*> item; not_null<HistoryItem*> item;
int64 started = 0; // unixtime * 1000 int64 started = 0; // unixtime * 1000
QString path; QString path;
QStringList words;
base::flat_set<QChar> letters;
bool found = false;
}; };
bool sectionHasFloatingHeader() override; bool sectionHasFloatingHeader() override;
@ -94,6 +100,10 @@ private:
not_null<const Media::BaseLayout*> item, not_null<const Media::BaseLayout*> item,
not_null<const Media::BaseLayout*> previous) override; not_null<const Media::BaseLayout*> previous) override;
[[nodiscard]] bool searchMode() const;
void fillSearchIndex(Element &element);
[[nodiscard]] bool computeIsFound(const Element &element) const;
void itemRemoved(not_null<const HistoryItem*> item); void itemRemoved(not_null<const HistoryItem*> item);
void markLayoutsStale(); void markLayoutsStale();
void clearStaleLayouts(); void clearStaleLayouts();
@ -102,6 +112,7 @@ private:
void addPostponed(not_null<const Data::DownloadedId*> entry); void addPostponed(not_null<const Data::DownloadedId*> entry);
void performRefresh(); void performRefresh();
void performAdd(); void performAdd();
void addElementNow(Element &&element);
void remove(not_null<const HistoryItem*> item); void remove(not_null<const HistoryItem*> item);
void trackItemSession(not_null<const HistoryItem*> item); void trackItemSession(not_null<const HistoryItem*> item);
@ -127,6 +138,10 @@ private:
rpl::event_stream<not_null<Media::BaseLayout*>> _layoutRemoved; rpl::event_stream<not_null<Media::BaseLayout*>> _layoutRemoved;
rpl::event_stream<> _refreshed; rpl::event_stream<> _refreshed;
QString _query;
QStringList _queryWords;
int _foundCount = 0;
base::flat_map<not_null<Main::Session*>, rpl::lifetime> _trackedSessions; base::flat_map<not_null<Main::Session*>, rpl::lifetime> _trackedSessions;
bool _postponedRefreshSort = false; bool _postponedRefreshSort = false;
bool _postponedRefresh = false; bool _postponedRefresh = false;

View file

@ -108,6 +108,10 @@ rpl::producer<QString> AbstractController::mediaSourceQueryValue() const {
return rpl::single(QString()); return rpl::single(QString());
} }
rpl::producer<QString> AbstractController::searchQueryValue() const {
return rpl::single(QString());
}
AbstractController::AbstractController( AbstractController::AbstractController(
not_null<Window::SessionController*> parent) not_null<Window::SessionController*> parent)
: SessionNavigation(&parent->session()) : SessionNavigation(&parent->session())
@ -221,8 +225,8 @@ void Controller::updateSearchControllers(
: Section::MediaType::kCount; : Section::MediaType::kCount;
const auto hasMediaSearch = isMedia const auto hasMediaSearch = isMedia
&& SharedMediaAllowSearch(mediaType); && SharedMediaAllowSearch(mediaType);
const auto hasCommonGroupsSearch const auto hasCommonGroupsSearch = (type == Type::CommonGroups);
= (type == Type::CommonGroups); const auto hasDownloadsSearch = (type == Type::Downloads);
const auto hasMembersSearch = (type == Type::Members) const auto hasMembersSearch = (type == Type::Members)
|| (type == Type::Profile); || (type == Type::Profile);
const auto searchQuery = memento->searchFieldQuery(); const auto searchQuery = memento->searchFieldQuery();
@ -236,7 +240,10 @@ void Controller::updateSearchControllers(
} else { } else {
_searchController = nullptr; _searchController = nullptr;
} }
if (hasMediaSearch || hasCommonGroupsSearch || hasMembersSearch) { if (hasMediaSearch
|| hasCommonGroupsSearch
|| hasDownloadsSearch
|| hasMembersSearch) {
_searchFieldController _searchFieldController
= std::make_unique<Ui::SearchFieldController>( = std::make_unique<Ui::SearchFieldController>(
searchQuery); searchQuery);
@ -300,9 +307,11 @@ rpl::producer<bool> Controller::searchEnabledByContent() const {
} }
rpl::producer<QString> Controller::mediaSourceQueryValue() const { rpl::producer<QString> Controller::mediaSourceQueryValue() const {
return _searchController return _searchController->currentQueryValue();
? _searchController->currentQueryValue() }
: rpl::single(QString()); // #TODO downloads search
rpl::producer<QString> Controller::searchQueryValue() const {
return searchFieldController()->queryValue();
} }
rpl::producer<SparseIdsMergedSlice> Controller::mediaSource( rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(

View file

@ -141,6 +141,7 @@ public:
int limitBefore, int limitBefore,
int limitAfter) const; int limitAfter) const;
virtual rpl::producer<QString> mediaSourceQueryValue() const; virtual rpl::producer<QString> mediaSourceQueryValue() const;
virtual rpl::producer<QString> searchQueryValue() const;
void showSection( void showSection(
std::shared_ptr<Window::SectionMemento> memento, std::shared_ptr<Window::SectionMemento> memento,
@ -198,6 +199,7 @@ public:
int limitBefore, int limitBefore,
int limitAfter) const override; int limitAfter) const override;
rpl::producer<QString> mediaSourceQueryValue() const override; rpl::producer<QString> mediaSourceQueryValue() const override;
rpl::producer<QString> searchQueryValue() const override;
bool takeSearchStartsFocused() { bool takeSearchStartsFocused() {
return base::take(_searchStartsFocused); return base::take(_searchStartsFocused);
} }

View file

@ -158,6 +158,8 @@ public:
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
not_null<DocumentData*> document) = 0; not_null<DocumentData*> document) = 0;
virtual void setSearchQuery(QString query) = 0;
[[nodiscard]] virtual int64 scrollTopStatePosition( [[nodiscard]] virtual int64 scrollTopStatePosition(
not_null<HistoryItem*> item) = 0; not_null<HistoryItem*> item) = 0;
[[nodiscard]] virtual HistoryItem *scrollTopStateItem( [[nodiscard]] virtual HistoryItem *scrollTopStateItem(

View file

@ -162,6 +162,11 @@ void ListWidget::start() {
if (_controller->isDownloads()) { if (_controller->isDownloads()) {
_provider->refreshViewer(); _provider->refreshViewer();
_controller->searchQueryValue(
) | rpl::start_with_next([this](QString &&query) {
_provider->setSearchQuery(std::move(query));
}, lifetime());
} else { } else {
trackSession(&session()); trackSession(&session());

View file

@ -341,6 +341,10 @@ bool Provider::isAfter(
return (GetUniversalId(a) < GetUniversalId(b)); return (GetUniversalId(a) < GetUniversalId(b));
} }
void Provider::setSearchQuery(QString query) {
Unexpected("Media::Provider::setSearchQuery.");
}
SparseIdsMergedSlice::Key Provider::sliceKey( SparseIdsMergedSlice::Key Provider::sliceKey(
UniversalMsgId universalId) const { UniversalMsgId universalId) const {
using Key = SparseIdsMergedSlice::Key; using Key = SparseIdsMergedSlice::Key;

View file

@ -46,6 +46,8 @@ public:
not_null<const HistoryItem*> a, not_null<const HistoryItem*> a,
not_null<const HistoryItem*> b) override; not_null<const HistoryItem*> b) override;
void setSearchQuery(QString query) override;
ListItemSelectionData computeSelectionData( ListItemSelectionData computeSelectionData(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
TextSelection selection) override; TextSelection selection) override;