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());
_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());

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_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<const HistoryItem*> item) {
}
std::optional<int> Provider::fullCount() {
return _fullCount;
return _queryWords.empty()
? _fullCount
: (_foundCount || _fullCount.has_value())
? _foundCount
: std::optional<int>();
}
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<const HistoryItem*> 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<ListSection> Provider::fillSections(
not_null<Overview::Layout::Delegate*> delegate) {
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<ListSection> Provider::fillSections(
ListSection(Type::File, sectionDelegate()));
auto &section = 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<const HistoryItem*> item) {
remove(item);
}
@ -393,9 +482,13 @@ void Provider::applyDragSelection(
selected.clear();
return;
}
const auto search = !_queryWords.isEmpty();
auto chosen = base::flat_set<not_null<const HistoryItem*>>();
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(

View file

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

View file

@ -108,6 +108,10 @@ rpl::producer<QString> AbstractController::mediaSourceQueryValue() const {
return rpl::single(QString());
}
rpl::producer<QString> AbstractController::searchQueryValue() const {
return rpl::single(QString());
}
AbstractController::AbstractController(
not_null<Window::SessionController*> 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<Ui::SearchFieldController>(
searchQuery);
@ -300,9 +307,11 @@ rpl::producer<bool> Controller::searchEnabledByContent() const {
}
rpl::producer<QString> Controller::mediaSourceQueryValue() const {
return _searchController
? _searchController->currentQueryValue()
: rpl::single(QString()); // #TODO downloads search
return _searchController->currentQueryValue();
}
rpl::producer<QString> Controller::searchQueryValue() const {
return searchFieldController()->queryValue();
}
rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(

View file

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

View file

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

View file

@ -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());

View file

@ -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;

View file

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