Implement public posts hashtag search preview.

This commit is contained in:
John Preston 2024-10-11 22:01:55 +04:00
parent 81492b7d3a
commit cec9688d58
4 changed files with 684 additions and 368 deletions

View file

@ -88,6 +88,7 @@ namespace {
constexpr auto kHashtagResultsLimit = 5; constexpr auto kHashtagResultsLimit = 5;
constexpr auto kStartReorderThreshold = 30; constexpr auto kStartReorderThreshold = 30;
constexpr auto kQueryPreviewLimit = 32; constexpr auto kQueryPreviewLimit = 32;
constexpr auto kPreviewPostsLimit = 3;
[[nodiscard]] int FixedOnTopDialogsCount(not_null<Dialogs::IndexedList*> list) { [[nodiscard]] int FixedOnTopDialogsCount(not_null<Dialogs::IndexedList*> list) {
auto result = 0; auto result = 0;
@ -555,7 +556,7 @@ int InnerWidget::searchInChatSkip() const {
return _searchIn ? _searchIn->height() : 0; return _searchIn ? _searchIn->height() : 0;
} }
int InnerWidget::searchedOffset() const { int InnerWidget::previewOffset() const {
auto result = peerSearchOffset(); auto result = peerSearchOffset();
if (!_peerSearchResults.empty()) { if (!_peerSearchResults.empty()) {
result += (_peerSearchResults.size() * st::dialogsRowHeight) result += (_peerSearchResults.size() * st::dialogsRowHeight)
@ -564,6 +565,15 @@ int InnerWidget::searchedOffset() const {
return result; return result;
} }
int InnerWidget::searchedOffset() const {
auto result = previewOffset();
if (!_previewResults.empty()) {
result += (_previewResults.size() * st::dialogsRowHeight)
+ st::searchedBarHeight;
}
return result;
}
void InnerWidget::changeOpenedFolder(Data::Folder *folder) { void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
Expects(!folder || !_savedSublists); Expects(!folder || !_savedSublists);
@ -810,7 +820,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
} }
if (_searchIn) { if (_searchIn) {
p.translate(0, searchInChatSkip()); p.translate(0, searchInChatSkip());
if (_searchResults.empty()) { if (_previewResults.empty() && _searchResults.empty()) {
p.fillRect(0, 0, fullWidth, st::lineWidth, st::shadowFg); p.fillRect(0, 0, fullWidth, st::lineWidth, st::shadowFg);
} }
} }
@ -924,7 +934,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
} }
const auto showUnreadInSearchResults = uniqueSearchResults(); const auto showUnreadInSearchResults = uniqueSearchResults();
if (_searchResults.empty()) { if (_previewResults.empty() && _searchResults.empty()) {
if (_loadingAnimation) { if (_loadingAnimation) {
const auto text = tr::lng_contacts_loading(tr::now); const auto text = tr::lng_contacts_loading(tr::now);
p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg);
@ -933,7 +943,68 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
p.translate(0, st::searchedBarHeight); p.translate(0, st::searchedBarHeight);
} }
} else { return;
}
if (!_previewResults.empty()) {
const auto text = tr::lng_search_tab_public_posts(tr::now);
p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg);
p.setFont(st::searchedBarFont);
p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
const auto moreFont = (_selectedMorePosts || _pressedMorePosts)
? st::searchedBarFont->underline()
: st::searchedBarFont;
{
const auto text = tr::lng_channels_your_more(tr::now);
if (!_morePostsWidth) {
_morePostsWidth = moreFont->width(text);
}
p.setFont(moreFont);
p.drawTextLeft(
width() - st::searchedBarPosition.x() - _morePostsWidth,
st::searchedBarPosition.y(),
width(),
text);
p.translate(0, st::searchedBarHeight);
}
auto skip = previewOffset();
auto from = floorclamp(r.y() - skip, _st->height, 0, _previewResults.size());
auto to = ceilclamp(r.y() + r.height() - skip, _st->height, 0, _previewResults.size());
p.translate(0, from * _st->height);
if (from < _previewResults.size()) {
for (; from < to; ++from) {
const auto &result = _previewResults[from];
const auto active = isSearchResultActive(result.get(), activeEntry);
const auto selected = _menuRow.key
? isSearchResultActive(result.get(), _menuRow)
: _chatPreviewRow.key
? isSearchResultActive(result.get(), _chatPreviewRow)
: (from == (isPressed()
? _previewPressed
: _previewSelected));
Ui::RowPainter::Paint(p, result.get(), {
.st = _st,
.folder = _openedFolder,
.forum = _openedForum,
.currentBg = currentBg(),
.filter = _filterId,
.now = ms,
.width = fullWidth,
.active = active,
.selected = selected,
.paused = videoPaused,
.search = true,
.narrow = (fullWidth < st::columnMinimalWidthLeft / 2),
.displayUnreadInfo = showUnreadInSearchResults,
});
p.translate(0, _st->height);
}
}
if (to < _previewResults.size()) {
p.translate(0, (_previewResults.size() - to) * _st->height);
}
}
if (!_searchResults.empty()) {
const auto text = showUnreadInSearchResults const auto text = showUnreadInSearchResults
? u"Search results"_q ? u"Search results"_q
: tr::lng_search_found_results( : tr::lng_search_found_results(
@ -1330,6 +1401,8 @@ void InnerWidget::clearIrrelevantState() {
setFilteredPressed(-1, false); setFilteredPressed(-1, false);
_peerSearchSelected = -1; _peerSearchSelected = -1;
setPeerSearchPressed(-1); setPeerSearchPressed(-1);
_previewSelected = -1;
setPreviewPressed(-1);
_searchedSelected = -1; _searchedSelected = -1;
setSearchedPressed(-1); setSearchedPressed(-1);
} else if (_state == WidgetState::Filtered) { } else if (_state == WidgetState::Filtered) {
@ -1446,6 +1519,32 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
updateSelectedRow(); updateSelectedRow();
} }
} }
if (!_previewResults.empty()) {
auto skip = previewOffset();
auto previewSelected = (mouseY >= skip) ? ((mouseY - skip) / _st->height) : -1;
if (previewSelected < 0 || previewSelected >= _previewResults.size()) {
previewSelected = -1;
}
if (_previewSelected != previewSelected) {
updateSelectedRow();
_previewSelected = previewSelected;
updateSelectedRow();
}
auto selectedMorePosts = false;
const auto from = skip - st::searchedBarHeight;
if (mouseY <= skip && mouseY >= from) {
const auto left = width()
- _morePostsWidth
- 2 * st::searchedBarPosition.x();
if (_morePostsWidth > 0 && local.x() >= left) {
selectedMorePosts = true;
}
}
if (_selectedMorePosts != selectedMorePosts) {
update(0, from, width(), st::searchedBarHeight);
_selectedMorePosts = selectedMorePosts;
}
}
if (!_searchResults.empty()) { if (!_searchResults.empty()) {
auto skip = searchedOffset(); auto skip = searchedOffset();
auto searchedSelected = (mouseY >= skip) ? ((mouseY - skip) / _st->height) : -1; auto searchedSelected = (mouseY >= skip) ? ((mouseY - skip) / _st->height) : -1;
@ -1495,7 +1594,9 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
_hashtagDeletePressed = _hashtagDeleteSelected; _hashtagDeletePressed = _hashtagDeleteSelected;
setFilteredPressed(_filteredSelected, _selectedTopicJump); setFilteredPressed(_filteredSelected, _selectedTopicJump);
setPeerSearchPressed(_peerSearchSelected); setPeerSearchPressed(_peerSearchSelected);
setPreviewPressed(_previewSelected);
setSearchedPressed(_searchedSelected); setSearchedPressed(_searchedSelected);
_pressedMorePosts = _selectedMorePosts;
const auto alt = (e->modifiers() & Qt::AltModifier); const auto alt = (e->modifiers() & Qt::AltModifier);
if (alt && showChatPreview()) { if (alt && showChatPreview()) {
@ -1862,8 +1963,12 @@ void InnerWidget::mousePressReleased(
setFilteredPressed(-1, false); setFilteredPressed(-1, false);
auto peerSearchPressed = _peerSearchPressed; auto peerSearchPressed = _peerSearchPressed;
setPeerSearchPressed(-1); setPeerSearchPressed(-1);
auto previewPressed = _previewPressed;
setPreviewPressed(-1);
auto searchedPressed = _searchedPressed; auto searchedPressed = _searchedPressed;
setSearchedPressed(-1); setSearchedPressed(-1);
const auto pressedMorePosts = _pressedMorePosts;
_pressedMorePosts = false;
if (wasDragging) { if (wasDragging) {
selectByMouse(globalPosition); selectByMouse(globalPosition);
} }
@ -1879,8 +1984,12 @@ void InnerWidget::mousePressReleased(
|| (filteredPressed >= 0 && filteredPressed == _filteredSelected) || (filteredPressed >= 0 && filteredPressed == _filteredSelected)
|| (peerSearchPressed >= 0 || (peerSearchPressed >= 0
&& peerSearchPressed == _peerSearchSelected) && peerSearchPressed == _peerSearchSelected)
|| (previewPressed >= 0
&& previewPressed == _previewSelected)
|| (searchedPressed >= 0 || (searchedPressed >= 0
&& searchedPressed == _searchedSelected)) { && searchedPressed == _searchedSelected)
|| (pressedMorePosts
&& pressedMorePosts == _selectedMorePosts)) {
chooseRow(modifiers, pressedTopicRootId); chooseRow(modifiers, pressedTopicRootId);
} }
} }
@ -1955,6 +2064,13 @@ void InnerWidget::setPeerSearchPressed(int pressed) {
_peerSearchPressed = pressed; _peerSearchPressed = pressed;
} }
void InnerWidget::setPreviewPressed(int pressed) {
if (base::in_range(_previewPressed, 0, _previewResults.size())) {
_previewResults[_previewPressed]->stopLastRipple();
}
_previewPressed = pressed;
}
void InnerWidget::setSearchedPressed(int pressed) { void InnerWidget::setSearchedPressed(int pressed) {
if (base::in_range(_searchedPressed, 0, _searchResults.size())) { if (base::in_range(_searchedPressed, 0, _searchResults.size())) {
_searchResults[_searchedPressed]->stopLastRipple(); _searchResults[_searchedPressed]->stopLastRipple();
@ -2313,6 +2429,8 @@ void InnerWidget::updateSelectedRow(Key key) {
} }
} else if (_peerSearchSelected >= 0) { } else if (_peerSearchSelected >= 0) {
update(0, peerSearchOffset() + _peerSearchSelected * st::dialogsRowHeight, width(), st::dialogsRowHeight); update(0, peerSearchOffset() + _peerSearchSelected * st::dialogsRowHeight, width(), st::dialogsRowHeight);
} else if (_previewSelected >= 0) {
update(0, previewOffset() + _previewSelected * _st->height, width(), _st->height);
} else if (_searchedSelected >= 0) { } else if (_searchedSelected >= 0) {
update(0, searchedOffset() + _searchedSelected * _st->height, width(), _st->height); update(0, searchedOffset() + _searchedSelected * _st->height, width(), _st->height);
} }
@ -2354,9 +2472,11 @@ void InnerWidget::clearSelection() {
if (isSelected()) { if (isSelected()) {
updateSelectedRow(); updateSelectedRow();
_collapsedSelected = -1; _collapsedSelected = -1;
_selectedMorePosts = false;
_selected = nullptr; _selected = nullptr;
_filteredSelected _filteredSelected
= _searchedSelected = _searchedSelected
= _previewSelected
= _peerSearchSelected = _peerSearchSelected
= _hashtagSelected = _hashtagSelected
= -1; = -1;
@ -2449,6 +2569,11 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
} else if (_state == WidgetState::Filtered) { } else if (_state == WidgetState::Filtered) {
if (base::in_range(_filteredSelected, 0, _filterResults.size())) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) {
return { _filterResults[_filteredSelected].key(), FullMsgId() }; return { _filterResults[_filteredSelected].key(), FullMsgId() };
} else if (base::in_range(_previewSelected, 0, _previewResults.size())) {
return {
_previewResults[_previewSelected]->item()->history(),
_previewResults[_previewSelected]->item()->fullId()
};
} else if (base::in_range(_searchedSelected, 0, _searchResults.size())) { } else if (base::in_range(_searchedSelected, 0, _searchResults.size())) {
return { return {
_searchResults[_searchedSelected]->item()->history(), _searchResults[_searchedSelected]->item()->history(),
@ -2597,6 +2722,7 @@ void InnerWidget::searchRequested(bool loading) {
_searchLoading = loading; _searchLoading = loading;
if (loading) { if (loading) {
clearSearchResults(true); clearSearchResults(true);
clearPreviewResults();
} }
refresh(true); refresh(true);
} }
@ -2660,6 +2786,7 @@ void InnerWidget::applySearchState(SearchState state) {
} }
_searchState = std::move(state); _searchState = std::move(state);
_searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query); _searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query);
_searchWithPostsPreview = computeSearchWithPostsPreview();
updateSearchIn(); updateSearchIn();
moveSearchIn(); moveSearchIn();
@ -2767,7 +2894,7 @@ void InnerWidget::appendToFiltered(Key key) {
const auto height = filteredHeight(); const auto height = filteredHeight();
_filterResults.emplace_back(i->second.get()); _filterResults.emplace_back(i->second.get());
_filterResults.back().top = height; _filterResults.back().top = height;
trackSearchResultsHistory(key.owningHistory()); trackResultsHistory(key.owningHistory());
} }
InnerWidget::~InnerWidget() { InnerWidget::~InnerWidget() {
@ -2780,13 +2907,16 @@ void InnerWidget::clearSearchResults(bool clearPeerSearchResults) {
_peerSearchResults.clear(); _peerSearchResults.clear();
} }
_searchResults.clear(); _searchResults.clear();
_searchResultsLifetime.destroy();
_searchResultsHistories.clear();
_searchedCount = _searchedMigratedCount = 0; _searchedCount = _searchedMigratedCount = 0;
} }
void InnerWidget::trackSearchResultsHistory(not_null<History*> history) { void InnerWidget::clearPreviewResults() {
if (!_searchResultsHistories.emplace(history).second) { _previewResults.clear();
_previewCount = 0;
}
void InnerWidget::trackResultsHistory(not_null<History*> history) {
if (!_trackedHistories.emplace(history).second) {
return; return;
} }
const auto channel = history->peer->asChannel(); const auto channel = history->peer->asChannel();
@ -2827,7 +2957,7 @@ void InnerWidget::trackSearchResultsHistory(not_null<History*> history) {
clearMouseSelection(true); clearMouseSelection(true);
} }
update(); update();
}, _searchResultsLifetime); }, _trackedLifetime);
if (const auto forum = channel->forum()) { if (const auto forum = channel->forum()) {
forum->topicDestroyed( forum->topicDestroyed(
@ -2857,7 +2987,7 @@ void InnerWidget::trackSearchResultsHistory(not_null<History*> history) {
if (_chatPreviewRow.key.topic() == topic) { if (_chatPreviewRow.key.topic() == topic) {
_chatPreviewRow = {}; _chatPreviewRow = {};
} }
}, _searchResultsLifetime); }, _trackedLifetime);
} }
} }
@ -2875,6 +3005,13 @@ Data::Thread *InnerWidget::updateFromParentDrag(QPoint globalPosition) {
} else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) { } else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) {
return session().data().history( return session().data().history(
_peerSearchResults[_peerSearchSelected]->peer); _peerSearchResults[_peerSearchSelected]->peer);
} else if (base::in_range(_previewSelected, 0, _previewResults.size())) {
if (const auto item = _previewResults[_previewSelected]->item()) {
if (const auto topic = item->topic()) {
return topic;
}
return item->history();
}
} else if (base::in_range(_searchedSelected, 0, _searchResults.size())) { } else if (base::in_range(_searchedSelected, 0, _searchResults.size())) {
if (const auto item = _searchResults[_searchedSelected]->item()) { if (const auto item = _searchResults[_searchedSelected]->item()) {
if (const auto topic = item->topic()) { if (const auto topic = item->topic()) {
@ -3028,12 +3165,14 @@ void InnerWidget::searchReceived(
_searchLoading = false; _searchLoading = false;
const auto uniquePeers = uniqueSearchResults(); const auto uniquePeers = uniqueSearchResults();
if (type == SearchRequestType::FromStart const auto withPreview = _searchWithPostsPreview;
|| type == SearchRequestType::PeerFromStart) { const auto toPreview = withPreview && type.posts;
if (type.start && !type.migrated && (!withPreview || !type.posts)) {
clearSearchResults(false); clearSearchResults(false);
} }
const auto isMigratedSearch = (type == SearchRequestType::MigratedFromStart) if (!withPreview || toPreview) {
|| (type == SearchRequestType::MigratedFromOffset); clearPreviewResults();
}
const auto key = (!_openedForum || _searchState.inChat.topic()) const auto key = (!_openedForum || _searchState.inChat.topic())
? _searchState.inChat ? _searchState.inChat
@ -3042,34 +3181,40 @@ void InnerWidget::searchReceived(
&& (!_searchState.inChat && (!_searchState.inChat
|| inject->history() == _searchState.inChat.history())) { || inject->history() == _searchState.inChat.history())) {
Assert(_searchResults.empty()); Assert(_searchResults.empty());
Assert(!toPreview);
const auto index = int(_searchResults.size()); const auto index = int(_searchResults.size());
_searchResults.push_back( _searchResults.push_back(
std::make_unique<FakeRow>( std::make_unique<FakeRow>(
key, key,
inject, inject,
[=] { repaintSearchResult(index); })); [=] { repaintSearchResult(index); }));
trackSearchResultsHistory(inject->history()); trackResultsHistory(inject->history());
++fullCount; ++fullCount;
} }
auto &results = toPreview ? _previewResults : _searchResults;
for (const auto &item : messages) { for (const auto &item : messages) {
const auto history = item->history(); const auto history = item->history();
if (!uniquePeers || !hasHistoryInResults(history)) { if (toPreview || !uniquePeers || !hasHistoryInResults(history)) {
const auto index = int(_searchResults.size()); const auto index = int(results.size());
_searchResults.push_back( const auto repaint = toPreview
std::make_unique<FakeRow>( ? Fn<void()>([=] { repaintSearchResult(index); })
key, : [=] { repaintPreviewResult(index); };
item, results.push_back(
[=] { repaintSearchResult(index); })); std::make_unique<FakeRow>(key, item, repaint));
trackSearchResultsHistory(history); trackResultsHistory(history);
if (uniquePeers && !history->unreadCountKnown()) { if (!toPreview && uniquePeers && !history->unreadCountKnown()) {
history->owner().histories().requestDialogEntry(history); history->owner().histories().requestDialogEntry(history);
} else if (toPreview && results.size() >= kPreviewPostsLimit) {
break;
} }
} }
} }
if (isMigratedSearch) { if (type.migrated) {
_searchedMigratedCount = fullCount; _searchedMigratedCount = fullCount;
} else { } else if (!withPreview || !toPreview) {
_searchedCount = fullCount; _searchedCount = fullCount;
} else {
_previewCount = fullCount;
} }
refresh(); refresh();
@ -3313,6 +3458,7 @@ void InnerWidget::clearMouseSelection(bool clearSelection) {
} else if (_state == WidgetState::Filtered) { } else if (_state == WidgetState::Filtered) {
_filteredSelected _filteredSelected
= _peerSearchSelected = _peerSearchSelected
= _previewSelected
= _searchedSelected = _searchedSelected
= _hashtagSelected = -1; = _hashtagSelected = -1;
} }
@ -3416,6 +3562,19 @@ void InnerWidget::repaintSearchResult(int index) {
_st->height); _st->height);
} }
void InnerWidget::repaintPreviewResult(int index) {
rtlupdate(
0,
previewOffset() + index * _st->height,
width(),
_st->height);
}
bool InnerWidget::computeSearchWithPostsPreview() const {
return (_searchHashOrCashtag != HashOrCashtag::None)
&& (_searchState.tab == ChatSearchTab::MyMessages);
}
void InnerWidget::clearFilter() { void InnerWidget::clearFilter() {
if (_state == WidgetState::Filtered || _searchState.inChat) { if (_state == WidgetState::Filtered || _searchState.inChat) {
if (_searchState.inChat) { if (_searchState.inChat) {
@ -3428,6 +3587,9 @@ void InnerWidget::clearFilter() {
_filterResultsGlobal.clear(); _filterResultsGlobal.clear();
_peerSearchResults.clear(); _peerSearchResults.clear();
_searchResults.clear(); _searchResults.clear();
_previewResults.clear();
_trackedHistories.clear();
_trackedLifetime.destroy();
_filter = QString(); _filter = QString();
refresh(true); refresh(true);
} }
@ -3474,15 +3636,22 @@ void InnerWidget::selectSkip(int32 direction) {
} }
scrollToDefaultSelected(); scrollToDefaultSelected();
} else if (_state == WidgetState::Filtered) { } else if (_state == WidgetState::Filtered) {
if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty() && _searchResults.empty()) { if (_hashtagResults.empty()
&& _filterResults.empty()
&& _peerSearchResults.empty()
&& _previewResults.empty()
&& _searchResults.empty()) {
return; return;
} }
if ((_hashtagSelected < 0 || _hashtagSelected >= _hashtagResults.size()) && if ((_hashtagSelected < 0 || _hashtagSelected >= _hashtagResults.size())
(_filteredSelected < 0 || _filteredSelected >= _filterResults.size()) && && (_filteredSelected < 0 || _filteredSelected >= _filterResults.size())
(_peerSearchSelected < 0 || _peerSearchSelected >= _peerSearchResults.size()) && && (_peerSearchSelected < 0 || _peerSearchSelected >= _peerSearchResults.size())
(_searchedSelected < 0 || _searchedSelected >= _searchResults.size())) { && (_previewSelected < 0 || _previewSelected >= _previewResults.size())
if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty()) { && (_searchedSelected < 0 || _searchedSelected >= _searchResults.size())) {
if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty() && _previewResults.empty()) {
_searchedSelected = 0; _searchedSelected = 0;
} else if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty()) {
_previewSelected = 0;
} else if (_hashtagResults.empty() && _filterResults.empty()) { } else if (_hashtagResults.empty() && _filterResults.empty()) {
_peerSearchSelected = 0; _peerSearchSelected = 0;
} else if (_hashtagResults.empty()) { } else if (_hashtagResults.empty()) {
@ -3493,30 +3662,36 @@ void InnerWidget::selectSkip(int32 direction) {
} else { } else {
int32 cur = base::in_range(_hashtagSelected, 0, _hashtagResults.size()) int32 cur = base::in_range(_hashtagSelected, 0, _hashtagResults.size())
? _hashtagSelected ? _hashtagSelected
: (base::in_range(_filteredSelected, 0, _filterResults.size()) : base::in_range(_filteredSelected, 0, _filterResults.size())
? (_hashtagResults.size() + _filteredSelected) ? (_hashtagResults.size() + _filteredSelected)
: (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size()) : base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())
? (_peerSearchSelected + _filterResults.size() + _hashtagResults.size()) ? (_peerSearchSelected + _filterResults.size() + _hashtagResults.size())
: (_searchedSelected + _peerSearchResults.size() + _filterResults.size() + _hashtagResults.size()))); : base::in_range(_previewSelected, 0, _previewResults.size())
? (_previewSelected + _peerSearchResults.size() + _filterResults.size() + _hashtagResults.size())
: (_searchedSelected + _previewResults.size() + _peerSearchResults.size() + _filterResults.size() + _hashtagResults.size());
cur = std::clamp( cur = std::clamp(
cur + direction, cur + direction,
0, 0,
static_cast<int>(_hashtagResults.size() static_cast<int>(_hashtagResults.size()
+ _filterResults.size() + _filterResults.size()
+ _peerSearchResults.size() + _peerSearchResults.size()
+ _previewResults.size()
+ _searchResults.size()) - 1); + _searchResults.size()) - 1);
if (cur < _hashtagResults.size()) { if (cur < _hashtagResults.size()) {
_hashtagSelected = cur; _hashtagSelected = cur;
_filteredSelected = _peerSearchSelected = _searchedSelected = -1; _filteredSelected = _peerSearchSelected = _previewSelected = _searchedSelected = -1;
} else if (cur < _hashtagResults.size() + _filterResults.size()) { } else if (cur < _hashtagResults.size() + _filterResults.size()) {
_filteredSelected = cur - _hashtagResults.size(); _filteredSelected = cur - _hashtagResults.size();
_hashtagSelected = _peerSearchSelected = _searchedSelected = -1; _hashtagSelected = _peerSearchSelected = _previewSelected = _searchedSelected = -1;
} else if (cur < _hashtagResults.size() + _filterResults.size() + _peerSearchResults.size()) { } else if (cur < _hashtagResults.size() + _filterResults.size() + _peerSearchResults.size()) {
_peerSearchSelected = cur - _hashtagResults.size() - _filterResults.size(); _peerSearchSelected = cur - _hashtagResults.size() - _filterResults.size();
_hashtagSelected = _filteredSelected = _searchedSelected = -1; _hashtagSelected = _filteredSelected = _previewSelected = _searchedSelected = -1;
} else if (cur < _hashtagResults.size() + _filterResults.size() + _peerSearchResults.size() + _previewResults.size()) {
_previewSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size();
_hashtagSelected = _filteredSelected = _peerSearchSelected = _searchedSelected = -1;
} else { } else {
_hashtagSelected = _filteredSelected = _peerSearchSelected = -1; _searchedSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size() - _previewResults.size();
_searchedSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size(); _hashtagSelected = _filteredSelected = _peerSearchSelected = _previewSelected = -1;
} }
} }
if (base::in_range(_hashtagSelected, 0, _hashtagResults.size())) { if (base::in_range(_hashtagSelected, 0, _hashtagResults.size())) {
@ -3533,6 +3708,13 @@ void InnerWidget::selectSkip(int32 direction) {
const auto height = st::dialogsRowHeight const auto height = st::dialogsRowHeight
+ (_peerSearchSelected ? 0 : st::searchedBarHeight); + (_peerSearchSelected ? 0 : st::searchedBarHeight);
scrollToItem(from, height); scrollToItem(from, height);
} else if (base::in_range(_previewSelected, 0, _previewResults.size())) {
const auto from = previewOffset()
+ _previewSelected * _st->height
+ (_previewSelected ? 0 : -st::searchedBarHeight);
const auto height = _st->height
+ (_previewSelected ? 0 : st::searchedBarHeight);
scrollToItem(from, height);
} else { } else {
const auto from = searchedOffset() const auto from = searchedOffset()
+ _searchedSelected * _st->height + _searchedSelected * _st->height
@ -3551,7 +3733,14 @@ void InnerWidget::scrollToEntry(const RowDescriptor &entry) {
scrollToItem(dialogsOffset() + row->top(), row->height()); scrollToItem(dialogsOffset() + row->top(), row->height());
} }
} else if (_state == WidgetState::Filtered) { } else if (_state == WidgetState::Filtered) {
for (int32 i = 0, c = _searchResults.size(); i < c; ++i) { for (auto i = 0, c = int(_previewResults.size()); i != c; ++i) {
if (isSearchResultActive(_previewResults[i].get(), entry)) {
const auto from = previewOffset() + i * _st->height;
scrollToItem(from, _st->height);
return;
}
}
for (auto i = 0, c = int(_searchResults.size()); i != c; ++i) {
if (isSearchResultActive(_searchResults[i].get(), entry)) { if (isSearchResultActive(_searchResults[i].get(), entry)) {
const auto from = searchedOffset() + i * _st->height; const auto from = searchedOffset() + i * _st->height;
scrollToItem(from, _st->height); scrollToItem(from, _st->height);
@ -3646,33 +3835,45 @@ void InnerWidget::preloadRowsData() {
} }
yTo -= otherStart; yTo -= otherStart;
} else if (_state == WidgetState::Filtered) { } else if (_state == WidgetState::Filtered) {
int32 from = (yFrom - filteredOffset()) / _st->height; auto from = (yFrom - filteredOffset()) / _st->height;
if (from < 0) from = 0; if (from < 0) from = 0;
if (from < _filterResults.size()) { if (from < _filterResults.size()) {
int32 to = (yTo / _st->height) + 1; const auto to = std::min(
if (to > _filterResults.size()) to = _filterResults.size(); ((yTo - filteredOffset()) / _st->height) + 1,
int(_filterResults.size()));
for (; from < to; ++from) { for (; from < to; ++from) {
_filterResults[from].key().entry()->chatListPreloadData(); _filterResults[from].key().entry()->chatListPreloadData();
} }
} }
from = (yFrom > filteredOffset() + st::searchedBarHeight ? ((yFrom - filteredOffset() - st::searchedBarHeight) / st::dialogsRowHeight) : 0) - _filterResults.size(); from = (yFrom - peerSearchOffset()) / st::dialogsRowHeight;
if (from < 0) from = 0; if (from < 0) from = 0;
if (from < _peerSearchResults.size()) { if (from < _peerSearchResults.size()) {
int32 to = (yTo > filteredOffset() + st::searchedBarHeight ? ((yTo - filteredOffset() - st::searchedBarHeight) / st::dialogsRowHeight) : 0) - _filterResults.size() + 1; const auto to = std::min(
if (to > _peerSearchResults.size()) to = _peerSearchResults.size(); ((yTo - peerSearchOffset()) / st::dialogsRowHeight) + 1,
int(_peerSearchResults.size()));
for (; from < to; ++from) { for (; from < to; ++from) {
_peerSearchResults[from]->peer->loadUserpic(); _peerSearchResults[from]->peer->loadUserpic();
} }
} }
from = (yFrom > filteredOffset() + ((_peerSearchResults.empty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight) ? ((yFrom - filteredOffset() - (_peerSearchResults.empty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / st::dialogsRowHeight) : 0) - _filterResults.size() - _peerSearchResults.size();
from = (yFrom - previewOffset()) / _st->height;
if (from < 0) from = 0;
if (from < _previewResults.size()) {
const auto to = std::min(
((yTo - previewOffset()) / _st->height) + 1,
int(_previewResults.size()));
for (; from < to; ++from) {
_previewResults[from]->item()->history()->peer->loadUserpic();
}
}
from = (yFrom - searchedOffset()) / _st->height;
if (from < 0) from = 0; if (from < 0) from = 0;
if (from < _searchResults.size()) { if (from < _searchResults.size()) {
int32 to = (yTo > filteredOffset() + (_peerSearchResults.empty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight ? ((yTo - filteredOffset() - (_peerSearchResults.empty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / st::dialogsRowHeight) : 0) - _filterResults.size() - _peerSearchResults.size() + 1; const auto to = std::min(
if (to > _searchResults.size()) to = _searchResults.size(); ((yTo - searchedOffset()) / _st->height) + 1,
int(_searchResults.size()));
for (; from < to; ++from) { for (; from < to; ++from) {
_searchResults[from]->item()->history()->peer->loadUserpic(); _searchResults[from]->item()->history()->peer->loadUserpic();
} }
@ -3803,6 +4004,14 @@ ChosenRow InnerWidget::computeChosenRow() const {
.key = session().data().history(peer), .key = session().data().history(peer),
.message = Data::UnreadMessagePosition .message = Data::UnreadMessagePosition
}; };
} else if (base::in_range(_previewSelected, 0, _previewResults.size())) {
const auto result = _previewResults[_previewSelected].get();
const auto topic = result->topic();
const auto item = result->item();
return {
.key = (topic ? (Entry*)topic : (Entry*)item->history()),
.message = item->position()
};
} else if (base::in_range(_searchedSelected, 0, _searchResults.size())) { } else if (base::in_range(_searchedSelected, 0, _searchResults.size())) {
const auto result = _searchResults[_searchedSelected].get(); const auto result = _searchResults[_searchedSelected].get();
const auto topic = result->topic(); const auto topic = result->topic();
@ -3832,6 +4041,11 @@ bool InnerWidget::chooseRow(
MsgId pressedTopicRootId) { MsgId pressedTopicRootId) {
if (chooseHashtag()) { if (chooseHashtag()) {
return true; return true;
} else if (_selectedMorePosts) {
if (_searchHashOrCashtag != HashOrCashtag::None) {
_changeSearchTabRequests.fire(ChatSearchTab::PublicPosts);
}
return true;
} }
const auto modifyChosenRow = [&]( const auto modifyChosenRow = [&](
ChosenRow row, ChosenRow row,

View file

@ -72,13 +72,18 @@ struct ChosenRow {
bool newWindow : 1 = false; bool newWindow : 1 = false;
}; };
enum class SearchRequestType : uchar { struct SearchRequestType {
FromStart, bool migrated : 1 = false;
FromOffset, bool posts : 1 = false;
PeerFromStart, bool start : 1 = false;
PeerFromOffset, bool peer : 1 = false;
MigratedFromStart,
MigratedFromOffset, friend inline constexpr auto operator<=>(
SearchRequestType a,
SearchRequestType b) = default;
friend inline constexpr bool operator==(
SearchRequestType a,
SearchRequestType b) = default;
}; };
enum class SearchRequestDelay : uchar { enum class SearchRequestDelay : uchar {
@ -283,6 +288,7 @@ private:
void setHashtagPressed(int pressed); void setHashtagPressed(int pressed);
void setFilteredPressed(int pressed, bool pressedTopicJump); void setFilteredPressed(int pressed, bool pressedTopicJump);
void setPeerSearchPressed(int pressed); void setPeerSearchPressed(int pressed);
void setPreviewPressed(int pressed);
void setSearchedPressed(int pressed); void setSearchedPressed(int pressed);
bool isPressed() const { bool isPressed() const {
return (_collapsedPressed >= 0) return (_collapsedPressed >= 0)
@ -290,7 +296,9 @@ private:
|| (_hashtagPressed >= 0) || (_hashtagPressed >= 0)
|| (_filteredPressed >= 0) || (_filteredPressed >= 0)
|| (_peerSearchPressed >= 0) || (_peerSearchPressed >= 0)
|| (_searchedPressed >= 0); || (_previewPressed >= 0)
|| (_searchedPressed >= 0)
|| _pressedMorePosts;
} }
bool isSelected() const { bool isSelected() const {
return (_collapsedSelected >= 0) return (_collapsedSelected >= 0)
@ -298,7 +306,9 @@ private:
|| (_hashtagSelected >= 0) || (_hashtagSelected >= 0)
|| (_filteredSelected >= 0) || (_filteredSelected >= 0)
|| (_peerSearchSelected >= 0) || (_peerSearchSelected >= 0)
|| (_searchedSelected >= 0); || (_previewSelected >= 0)
|| (_searchedSelected >= 0)
|| _selectedMorePosts;
} }
bool uniqueSearchResults() const; bool uniqueSearchResults() const;
bool hasHistoryInResults(not_null<History*> history) const; bool hasHistoryInResults(not_null<History*> history) const;
@ -352,6 +362,7 @@ private:
[[nodiscard]] int filteredHeight(int till = -1) const; [[nodiscard]] int filteredHeight(int till = -1) const;
[[nodiscard]] int peerSearchOffset() const; [[nodiscard]] int peerSearchOffset() const;
[[nodiscard]] int searchInChatOffset() const; [[nodiscard]] int searchInChatOffset() const;
[[nodiscard]] int previewOffset() const;
[[nodiscard]] int searchedOffset() const; [[nodiscard]] int searchedOffset() const;
[[nodiscard]] int searchInChatSkip() const; [[nodiscard]] int searchInChatSkip() const;
[[nodiscard]] int hashtagsOffset() const; [[nodiscard]] int hashtagsOffset() const;
@ -403,14 +414,18 @@ private:
// const Ui::Text::String &text) const; // const Ui::Text::String &text) const;
void updateSearchIn(); void updateSearchIn();
void repaintSearchResult(int index); void repaintSearchResult(int index);
void repaintPreviewResult(int index);
[[nodiscard]] bool computeSearchWithPostsPreview() const;
Ui::VideoUserpic *validateVideoUserpic(not_null<Row*> row); Ui::VideoUserpic *validateVideoUserpic(not_null<Row*> row);
Ui::VideoUserpic *validateVideoUserpic(not_null<History*> history); Ui::VideoUserpic *validateVideoUserpic(not_null<History*> history);
Row *shownRowByKey(Key key); Row *shownRowByKey(Key key);
void clearSearchResults(bool clearPeerSearchResults = true); void clearSearchResults(bool clearPeerSearchResults = true);
void clearPreviewResults();
void updateSelectedRow(Key key = Key()); void updateSelectedRow(Key key = Key());
void trackSearchResultsHistory(not_null<History*> history); void trackResultsHistory(not_null<History*> history);
[[nodiscard]] QBrush currentBg() const; [[nodiscard]] QBrush currentBg() const;
[[nodiscard]] RowDescriptor computeChatPreviewRow() const; [[nodiscard]] RowDescriptor computeChatPreviewRow() const;
@ -449,6 +464,8 @@ private:
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows; std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
not_null<const style::DialogRow*> _st; not_null<const style::DialogRow*> _st;
mutable std::unique_ptr<Ui::TopicJumpCache> _topicJumpCache; mutable std::unique_ptr<Ui::TopicJumpCache> _topicJumpCache;
bool _selectedMorePosts = false;
bool _pressedMorePosts = false;
int _collapsedSelected = -1; int _collapsedSelected = -1;
int _collapsedPressed = -1; int _collapsedPressed = -1;
bool _skipTopDialog = false; bool _skipTopDialog = false;
@ -487,14 +504,21 @@ private:
EmptyState _emptyState = EmptyState::None; EmptyState _emptyState = EmptyState::None;
base::flat_set<not_null<History*>> _trackedHistories;
rpl::lifetime _trackedLifetime;
QString _peerSearchQuery; QString _peerSearchQuery;
std::vector<std::unique_ptr<PeerSearchResult>> _peerSearchResults; std::vector<std::unique_ptr<PeerSearchResult>> _peerSearchResults;
int _peerSearchSelected = -1; int _peerSearchSelected = -1;
int _peerSearchPressed = -1; int _peerSearchPressed = -1;
std::vector<std::unique_ptr<FakeRow>> _previewResults;
int _previewCount = 0;
int _previewSelected = -1;
int _previewPressed = -1;
int _morePostsWidth = 0;
std::vector<std::unique_ptr<FakeRow>> _searchResults; std::vector<std::unique_ptr<FakeRow>> _searchResults;
base::flat_set<not_null<History*>> _searchResultsHistories;
rpl::lifetime _searchResultsLifetime;
int _searchedCount = 0; int _searchedCount = 0;
int _searchedMigratedCount = 0; int _searchedMigratedCount = 0;
int _searchedSelected = -1; int _searchedSelected = -1;
@ -516,6 +540,7 @@ private:
SearchState _searchState; SearchState _searchState;
HashOrCashtag _searchHashOrCashtag = {}; HashOrCashtag _searchHashOrCashtag = {};
bool _searchWithPostsPreview = false;
History *_searchInMigrated = nullptr; History *_searchInMigrated = nullptr;
PeerData *_searchFromShown = nullptr; PeerData *_searchFromShown = nullptr;
Ui::Text::String _searchFromUserText; Ui::Text::String _searchFromUserText;

View file

@ -234,7 +234,9 @@ void Widget::BottomButton::radialAnimationCallback() {
} }
} }
void Widget::BottomButton::onStateChanged(State was, StateChangeSource source) { void Widget::BottomButton::onStateChanged(
State was,
StateChangeSource source) {
RippleButton::onStateChanged(was, source); RippleButton::onStateChanged(was, source);
if ((was & StateFlag::Disabled) != (state() & StateFlag::Disabled)) { if ((was & StateFlag::Disabled) != (state() & StateFlag::Disabled)) {
_loading = isDisabled() _loading = isDisabled()
@ -532,11 +534,11 @@ Widget::Widget(
}, lifetime()); }, lifetime());
} }
_cancelSearch->setClickedCallback([this] { _cancelSearch->setClickedCallback([=] {
cancelSearch({ .jumpBackToSearchedChat = true }); cancelSearch({ .jumpBackToSearchedChat = true });
}); });
_jumpToDate->entity()->setClickedCallback([this] { showCalendar(); }); _jumpToDate->entity()->setClickedCallback([=] { showCalendar(); });
_chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); }); _chooseFromUser->entity()->setClickedCallback([=] { showSearchFrom(); });
rpl::single(rpl::empty) | rpl::then( rpl::single(rpl::empty) | rpl::then(
session().domain().local().localPasscodeChanged() session().domain().local().localPasscodeChanged()
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
@ -576,11 +578,10 @@ Widget::Widget(
}); });
_inner->setLoadMoreCallback([=] { _inner->setLoadMoreCallback([=] {
const auto state = _inner->state(); const auto state = _inner->state();
const auto process = currentSearchProcess();
if (state == WidgetState::Filtered if (state == WidgetState::Filtered
&& (!_searchFull && (!process->full
|| (_searchInMigrated || (_searchInMigrated && !_migratedProcess.full))) {
&& _searchFull
&& !_searchFullMigrated))) {
searchMore(); searchMore();
} else if (_openedForum && state == WidgetState::Default) { } else if (_openedForum && state == WidgetState::Default) {
_openedForum->requestTopics(); _openedForum->requestTopics();
@ -1199,11 +1200,12 @@ void Widget::fullSearchRefreshOn(rpl::producer<> events) {
return !_searchQuery.isEmpty(); return !_searchQuery.isEmpty();
}) | rpl::start_with_next([=] { }) | rpl::start_with_next([=] {
_searchTimer.cancel(); _searchTimer.cancel();
_searchCache.clear(); _searchProcess.cache.clear();
_singleMessageSearch.clear(); const auto queries = base::take(_searchProcess.queries);
for (const auto &[requestId, query] : base::take(_searchQueries)) { for (const auto &[requestId, query] : queries) {
session().api().request(requestId).cancel(); session().api().request(requestId).cancel();
} }
_singleMessageSearch.clear();
_searchQuery = QString(); _searchQuery = QString();
_scroll->scrollToY(0); _scroll->scrollToY(0);
cancelSearchRequest(); cancelSearchRequest();
@ -1529,6 +1531,7 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) {
_searchState.tab = forum _searchState.tab = forum
? ChatSearchTab::ThisPeer ? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages; : ChatSearchTab::MyMessages;
_searchWithPostsPreview = computeSearchWithPostsPreview();
_api.request(base::take(_topicSearchRequest)).cancel(); _api.request(base::take(_topicSearchRequest)).cancel();
_inner->changeOpenedForum(forum); _inner->changeOpenedForum(forum);
storiesToggleExplicitExpand(false); storiesToggleExplicitExpand(false);
@ -2110,12 +2113,18 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
const auto fromPeer = searchFromPeer(); const auto fromPeer = searchFromPeer();
const auto &inTags = searchInTags(); const auto &inTags = searchInTags();
const auto tab = _searchState.tab; const auto tab = _searchState.tab;
const auto fromStartType = inPeer const auto fromStartType = SearchRequestType{
? SearchRequestType::PeerFromStart .start = true,
: SearchRequestType::FromStart; .peer = (inPeer != nullptr),
};
if (trimmed.isEmpty() && !fromPeer && inTags.empty()) { if (trimmed.isEmpty() && !fromPeer && inTags.empty()) {
cancelSearchRequest(); cancelSearchRequest();
searchApplyEmpty(fromStartType, 0); searchApplyEmpty(fromStartType, currentSearchProcess());
if (_searchWithPostsPreview) {
searchApplyEmpty(
{ .posts = true, .start = true },
&_postsProcess);
}
_api.request(base::take(_peerSearchRequest)).cancel(); _api.request(base::take(_peerSearchRequest)).cancel();
_peerSearchQuery = QString(); _peerSearchQuery = QString();
peerSearchApplyEmpty(0); peerSearchApplyEmpty(0);
@ -2128,28 +2137,32 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
if (!success) { if (!success) {
return false; return false;
} }
const auto i = _searchCache.find(query); const auto process = currentSearchProcess();
if (i != _searchCache.end()) { const auto i = process->cache.find(query);
if (i != process->cache.end()) {
_searchQuery = query; _searchQuery = query;
_searchQueryFrom = fromPeer; _searchQueryFrom = fromPeer;
_searchQueryTags = inTags; _searchQueryTags = inTags;
_searchQueryTab = tab; _searchQueryTab = tab;
_searchNextRate = 0; process->nextRate = 0;
_searchFull = _searchFullMigrated = false; process->full = false;
_migratedProcess.full = false;
cancelSearchRequest(); cancelSearchRequest();
searchReceived(fromStartType, i->second, 0); searchReceived(fromStartType, i->second, process, true);
result = true; result = true;
} }
} else if (_searchQuery != query } else if (_searchQuery != query
|| _searchQueryFrom != fromPeer || _searchQueryFrom != fromPeer
|| _searchQueryTags != inTags || _searchQueryTags != inTags
|| _searchQueryTab != tab) { || _searchQueryTab != tab) {
const auto process = currentSearchProcess();
_searchQuery = query; _searchQuery = query;
_searchQueryFrom = fromPeer; _searchQueryFrom = fromPeer;
_searchQueryTags = inTags; _searchQueryTags = inTags;
_searchQueryTab = tab; _searchQueryTab = tab;
_searchNextRate = 0; process->nextRate = 0;
_searchFull = _searchFullMigrated = false; process->full = false;
_migratedProcess.full = false;
cancelSearchRequest(); cancelSearchRequest();
if (inPeer) { if (inPeer) {
const auto topic = searchInTopic(); const auto topic = searchInTopic();
@ -2163,83 +2176,55 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
const auto savedPeer = sublist const auto savedPeer = sublist
? sublist->peer().get() ? sublist->peer().get()
: nullptr; : nullptr;
_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) { _historiesRequest = histories.sendRequest(history, type, [=](
const auto type = SearchRequestType::PeerFromStart; Fn<void()> finish) {
const auto type = SearchRequestType{
.start = true,
.peer = true,
};
using Flag = MTPmessages_Search::Flag; using Flag = MTPmessages_Search::Flag;
_searchRequest = session().api().request(MTPmessages_Search( process->requestId = session().api().request(
MTP_flags((topic ? Flag::f_top_msg_id : Flag()) MTPmessages_Search(
| (fromPeer ? Flag::f_from_id : Flag()) MTP_flags((topic ? Flag::f_top_msg_id : Flag())
| (savedPeer ? Flag::f_saved_peer_id : Flag()) | (fromPeer ? Flag::f_from_id : Flag())
| (_searchQueryTags.empty() | (savedPeer ? Flag::f_saved_peer_id : Flag())
? Flag() | (_searchQueryTags.empty()
: Flag::f_saved_reaction)), ? Flag()
inPeer->input, : Flag::f_saved_reaction)),
MTP_string(_searchQuery), inPeer->input,
(fromPeer ? fromPeer->input : MTP_inputPeerEmpty()), MTP_string(_searchQuery),
(savedPeer ? savedPeer->input : MTP_inputPeerEmpty()), (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()),
MTP_vector_from_range( (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()),
_searchQueryTags | ranges::views::transform( MTP_vector_from_range(
Data::ReactionToMTP _searchQueryTags | ranges::views::transform(
)), Data::ReactionToMTP
MTP_int(topic ? topic->rootId() : 0), )),
MTP_inputMessagesFilterEmpty(), MTP_int(topic ? topic->rootId() : 0),
MTP_int(0), // min_date MTP_inputMessagesFilterEmpty(),
MTP_int(0), // max_date MTP_int(0), // min_date
MTP_int(0), // offset_id MTP_int(0), // max_date
MTP_int(0), // add_offset MTP_int(0), // offset_id
MTP_int(kSearchPerPage), MTP_int(0), // add_offset
MTP_int(0), // max_id MTP_int(kSearchPerPage),
MTP_int(0), // min_id MTP_int(0), // max_id
MTP_long(0) // hash MTP_int(0), // min_id
)).done([=](const MTPmessages_Messages &result) { MTP_long(0)) // hash
_searchInHistoryRequest = 0; ).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, _searchRequest); _historiesRequest = 0;
searchReceived(type, result, process);
finish(); finish();
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_searchInHistoryRequest = 0; _historiesRequest = 0;
searchFailed(type, error, _searchRequest); searchFailed(type, error, process);
finish(); finish();
}).send(); }).send();
_searchQueries.emplace(_searchRequest, _searchQuery); process->queries.emplace(process->requestId, _searchQuery);
return _searchRequest; return process->requestId;
}); });
} else if (_searchState.tab == ChatSearchTab::PublicPosts) { } else if (_searchState.tab == ChatSearchTab::PublicPosts) {
const auto type = SearchRequestType::FromStart; requestPublicPosts(true);
_searchRequest = session().api().request(MTPchannels_SearchPosts(
MTP_string(_searchState.query.trimmed().mid(1)),
MTP_int(0), // offset_rate
MTP_inputPeerEmpty(), // offset_peer
MTP_int(0), // offset_id
MTP_int(kSearchPerPage)
)).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, _searchRequest);
}).fail([=](const MTP::Error &error) {
searchFailed(type, error, _searchRequest);
}).send();
_searchQueries.emplace(_searchRequest, _searchQuery);
} else { } else {
const auto type = SearchRequestType::FromStart; requestMessages(true);
const auto flags = session().settings().skipArchiveInSearch()
? MTPmessages_SearchGlobal::Flag::f_folder_id
: MTPmessages_SearchGlobal::Flag(0);
const auto folderId = 0;
_searchRequest = session().api().request(MTPmessages_SearchGlobal(
MTP_flags(flags),
MTP_int(folderId),
MTP_string(_searchQuery),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
MTP_int(0), // max_date
MTP_int(0), // offset_rate
MTP_inputPeerEmpty(), // offset_peer
MTP_int(0), // offset_id
MTP_int(kSearchPerPage)
)).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, _searchRequest);
}).fail([=](const MTP::Error &error) {
searchFailed(type, error, _searchRequest);
}).send();
_searchQueries.emplace(_searchRequest, _searchQuery);
} }
_inner->searchRequested(true); _inner->searchRequested(true);
} else { } else {
@ -2261,7 +2246,9 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
_peerSearchRequest = _api.request(MTPcontacts_Search( _peerSearchRequest = _api.request(MTPcontacts_Search(
MTP_string(_peerSearchQuery), MTP_string(_peerSearchQuery),
MTP_int(SearchPeopleLimit) MTP_int(SearchPeopleLimit)
)).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { )).done([=](
const MTPcontacts_Found &result,
mtpRequestId requestId) {
peerSearchReceived(result, requestId); peerSearchReceived(result, requestId);
}).fail([=](const MTP::Error &error, mtpRequestId requestId) { }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
peerSearchFailed(error, requestId); peerSearchFailed(error, requestId);
@ -2368,11 +2355,12 @@ void Widget::searchTopics() {
} }
void Widget::searchMore() { void Widget::searchMore() {
if (_searchRequest const auto process = currentSearchProcess();
|| _searchInHistoryRequest if (process->requestId
|| _historiesRequest
|| _searchTimer.isActive()) { || _searchTimer.isActive()) {
return; return;
} else if (!_searchFull) { } else if (!process->full) {
if (const auto peer = searchInPeer()) { if (const auto peer = searchInPeer()) {
auto &histories = session().data().histories(); auto &histories = session().data().histories();
const auto topic = searchInTopic(); const auto topic = searchInTopic();
@ -2385,154 +2373,213 @@ void Widget::searchMore() {
const auto savedPeer = sublist const auto savedPeer = sublist
? sublist->peer().get() ? sublist->peer().get()
: nullptr; : nullptr;
_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) { _historiesRequest = histories.sendRequest(history, type, [=](
const auto type = _lastSearchId Fn<void()> finish) {
? SearchRequestType::PeerFromOffset const auto type = SearchRequestType{
: SearchRequestType::PeerFromStart; .start = !process->lastId,
.peer = true,
};
using Flag = MTPmessages_Search::Flag; using Flag = MTPmessages_Search::Flag;
_searchRequest = session().api().request(MTPmessages_Search( process->requestId = session().api().request(
MTP_flags((topic ? Flag::f_top_msg_id : Flag()) MTPmessages_Search(
| (fromPeer ? Flag::f_from_id : Flag()) MTP_flags((topic ? Flag::f_top_msg_id : Flag())
| (savedPeer ? Flag::f_saved_peer_id : Flag()) | (fromPeer ? Flag::f_from_id : Flag())
| (_searchQueryTags.empty() | (savedPeer ? Flag::f_saved_peer_id : Flag())
? Flag() | (_searchQueryTags.empty()
: Flag::f_saved_reaction)), ? Flag()
peer->input, : Flag::f_saved_reaction)),
peer->input,
MTP_string(_searchQuery),
(fromPeer ? fromPeer->input : MTP_inputPeerEmpty()),
(savedPeer
? savedPeer->input
: MTP_inputPeerEmpty()),
MTP_vector_from_range(
_searchQueryTags | ranges::views::transform(
Data::ReactionToMTP
)),
MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
MTP_int(0), // max_date
MTP_int(process->lastId),
MTP_int(0), // add_offset
MTP_int(kSearchPerPage),
MTP_int(0), // max_id
MTP_int(0), // min_id
MTP_long(0)) // hash
).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, process);
_historiesRequest = 0;
finish();
}).fail([=](const MTP::Error &error) {
searchFailed(type, error, process);
_historiesRequest = 0;
finish();
}).send();
if (!process->lastId) {
process->queries.emplace(
process->requestId,
_searchQuery);
}
return process->requestId;
});
} else if (_searchState.tab == ChatSearchTab::PublicPosts) {
requestPublicPosts(false);
} else {
requestMessages(false);
}
} else if (_searchInMigrated && !_migratedProcess.full) {
auto &histories = session().data().histories();
const auto type = Data::Histories::RequestType::History;
const auto history = _searchInMigrated;
_historiesRequest = histories.sendRequest(history, type, [=](
Fn<void()> finish) {
const auto type = SearchRequestType{
.migrated = true,
.start = !_migratedProcess.lastId,
};
const auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_migratedProcess.requestId = session().api().request(
MTPmessages_Search(
flags,
_searchInMigrated->peer->input,
MTP_string(_searchQuery), MTP_string(_searchQuery),
(fromPeer ? fromPeer->input : MTP_inputPeerEmpty()), (_searchQueryFrom
(savedPeer ? savedPeer->input : MTP_inputPeerEmpty()), ? _searchQueryFrom->input
MTP_vector_from_range( : MTP_inputPeerEmpty()),
_searchQueryTags | ranges::views::transform( MTPInputPeer(), // saved_peer_id
Data::ReactionToMTP MTPVector<MTPReaction>(), // saved_reaction
)), MTPint(), // top_msg_id
MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(), MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date MTP_int(0), // min_date
MTP_int(0), // max_date MTP_int(0), // max_date
MTP_int(_lastSearchId), MTP_int(_migratedProcess.lastId),
MTP_int(0), // add_offset MTP_int(0), // add_offset
MTP_int(kSearchPerPage), MTP_int(kSearchPerPage),
MTP_int(0), // max_id MTP_int(0), // max_id
MTP_int(0), // min_id MTP_int(0), // min_id
MTP_long(0) // hash MTP_long(0)) // hash
)).done([=](const MTPmessages_Messages &result) { ).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, _searchRequest); searchReceived(type, result, &_migratedProcess);
_searchInHistoryRequest = 0; _historiesRequest = 0;
finish();
}).fail([=](const MTP::Error &error) {
searchFailed(type, error, _searchRequest);
_searchInHistoryRequest = 0;
finish();
}).send();
if (!_lastSearchId) {
_searchQueries.emplace(_searchRequest, _searchQuery);
}
return _searchRequest;
});
} else {
const auto type = _lastSearchId
? SearchRequestType::FromOffset
: SearchRequestType::FromStart;
const auto flags = session().settings().skipArchiveInSearch()
? MTPmessages_SearchGlobal::Flag::f_folder_id
: MTPmessages_SearchGlobal::Flag(0);
const auto folderId = 0;
_searchRequest = session().api().request(MTPmessages_SearchGlobal(
MTP_flags(flags),
MTP_int(folderId),
MTP_string(_searchQuery),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
MTP_int(0), // max_date
MTP_int(_searchNextRate),
(_lastSearchPeer
? _lastSearchPeer->input
: MTP_inputPeerEmpty()),
MTP_int(_lastSearchId),
MTP_int(kSearchPerPage)
)).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, _searchRequest);
}).fail([=](const MTP::Error &error) {
searchFailed(type, error, _searchRequest);
}).send();
if (!_lastSearchId) {
_searchQueries.emplace(_searchRequest, _searchQuery);
}
}
} else if (_searchInMigrated && !_searchFullMigrated) {
auto &histories = session().data().histories();
const auto type = Data::Histories::RequestType::History;
const auto history = _searchInMigrated;
_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
const auto type = _lastSearchMigratedId
? SearchRequestType::MigratedFromOffset
: SearchRequestType::MigratedFromStart;
const auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_searchRequest = session().api().request(MTPmessages_Search(
flags,
_searchInMigrated->peer->input,
MTP_string(_searchQuery),
(_searchQueryFrom
? _searchQueryFrom->input
: MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
MTPVector<MTPReaction>(), // saved_reaction
MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
MTP_int(0), // max_date
MTP_int(_lastSearchMigratedId),
MTP_int(0), // add_offset
MTP_int(kSearchPerPage),
MTP_int(0), // max_id
MTP_int(0), // min_id
MTP_long(0) // hash
)).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, _searchRequest);
_searchInHistoryRequest = 0;
finish(); finish();
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
searchFailed(type, error, _searchRequest); searchFailed(type, error, &_migratedProcess);
_searchInHistoryRequest = 0; _historiesRequest = 0;
finish(); finish();
}).send(); }).send();
return _searchRequest; return _migratedProcess.requestId;
}); });
} }
} }
void Widget::requestPublicPosts(bool fromStart) {
if (!_postsProcess.lastId || !_postsProcess.lastPeer) {
fromStart = true;
}
const auto type = SearchRequestType{
.posts = true,
.start = fromStart,
};
_postsProcess.requestId = session().api().request(
MTPchannels_SearchPosts(
MTP_string(_searchState.query.trimmed().mid(1)),
MTP_int(fromStart ? 0 : _postsProcess.nextRate),
(fromStart
? MTP_inputPeerEmpty()
: _postsProcess.lastPeer->input),
MTP_int(fromStart ? 0 : _postsProcess.lastId),
MTP_int(kSearchPerPage))
).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, &_postsProcess);
}).fail([=](const MTP::Error &error) {
searchFailed(type, error, &_postsProcess);
}).send();
if (fromStart) {
_postsProcess.queries.emplace(_postsProcess.requestId, _searchQuery);
}
}
void Widget::requestMessages(bool fromStart) {
if (!_searchProcess.lastId || !_searchProcess.lastPeer) {
fromStart = true;
}
const auto type = SearchRequestType{
.start = fromStart,
};
const auto flags = session().settings().skipArchiveInSearch()
? MTPmessages_SearchGlobal::Flag::f_folder_id
: MTPmessages_SearchGlobal::Flag(0);
const auto folderId = 0;
_searchProcess.requestId = session().api().request(
MTPmessages_SearchGlobal(
MTP_flags(flags),
MTP_int(folderId),
MTP_string(_searchQuery),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
MTP_int(0), // max_date
MTP_int(fromStart ? 0 : _searchProcess.nextRate),
(fromStart
? MTP_inputPeerEmpty()
: _searchProcess.lastPeer->input),
MTP_int(fromStart ? 0 : _searchProcess.lastId),
MTP_int(kSearchPerPage))
).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, &_searchProcess);
}).fail([=](const MTP::Error &error) {
searchFailed(type, error, &_searchProcess);
}).send();
if (!_searchProcess.lastId) {
_searchProcess.queries.emplace(
_searchProcess.requestId,
_searchQuery);
}
if (fromStart && _searchWithPostsPreview) {
requestPublicPosts(true);
}
}
auto Widget::currentSearchProcess() -> not_null<SearchProcessState*> {
return (_searchState.tab == ChatSearchTab::PublicPosts)
? &_postsProcess
: &_searchProcess;
}
bool Widget::computeSearchWithPostsPreview() const {
return (_searchHashOrCashtag != HashOrCashtag::None)
&& (_searchState.tab == ChatSearchTab::MyMessages);
}
void Widget::searchReceived( void Widget::searchReceived(
SearchRequestType type, SearchRequestType type,
const MTPmessages_Messages &result, const MTPmessages_Messages &result,
mtpRequestId requestId) { not_null<SearchProcessState*> process,
bool cacheResults) {
const auto state = _inner->state(); const auto state = _inner->state();
if (state == WidgetState::Filtered) { if (!cacheResults
if (type == SearchRequestType::FromStart || type == SearchRequestType::PeerFromStart) { && (state == WidgetState::Filtered)
auto i = _searchQueries.find(requestId); && type.start) {
if (i != _searchQueries.end()) { const auto i = process->queries.find(process->requestId);
_searchCache[i->second] = result; if (i != process->queries.end()) {
_searchQueries.erase(i); process->cache[i->second] = result;
} process->queries.erase(i);
} }
} }
const auto inject = (type == SearchRequestType::FromStart const auto inject = (type.start && !type.posts)
|| type == SearchRequestType::PeerFromStart)
? *_singleMessageSearch.lookup(_searchQuery) ? *_singleMessageSearch.lookup(_searchQuery)
: nullptr; : nullptr;
if (cacheResults && process->requestId) {
if (_searchRequest != requestId) {
return; return;
} }
if (type == SearchRequestType::FromStart if (type.start) {
|| type == SearchRequestType::PeerFromStart) { process->lastPeer = nullptr;
_lastSearchPeer = nullptr; process->lastId = 0;
_lastSearchId = _lastSearchMigratedId = 0;
} }
const auto isMigratedSearch = (type == SearchRequestType::MigratedFromStart) const auto processList = [&](const MTPVector<MTPMessage> &messages) {
|| (type == SearchRequestType::MigratedFromOffset);
const auto process = [&](const MTPVector<MTPMessage> &messages) {
auto result = std::vector<not_null<HistoryItem*>>(); auto result = std::vector<not_null<HistoryItem*>>();
for (const auto &message : messages.v) { for (const auto &message : messages.v) {
const auto msgId = IdFromMessage(message); const auto msgId = IdFromMessage(message);
@ -2546,55 +2593,44 @@ void Widget::searchReceived(
NewMessageType::Existing); NewMessageType::Existing);
result.push_back(item); result.push_back(item);
} }
_lastSearchPeer = peer; process->lastPeer = peer;
} else { } else {
LOG(("API Error: a search results with not loaded peer %1" LOG(("API Error: a search results with not loaded peer %1"
).arg(peerId.value)); ).arg(peerId.value));
} }
if (isMigratedSearch) { process->lastId = msgId;
_lastSearchMigratedId = msgId;
} else {
_lastSearchId = msgId;
}
} }
return result; return result;
}; };
auto fullCount = 0; auto fullCount = 0;
auto messages = result.match([&](const MTPDmessages_messages &data) { auto messages = result.match([&](const MTPDmessages_messages &data) {
if (_searchRequest != 0) { if (!cacheResults) {
// Don't apply cached data! // Don't apply cached data!
session().data().processUsers(data.vusers()); session().data().processUsers(data.vusers());
session().data().processChats(data.vchats()); session().data().processChats(data.vchats());
} }
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { process->full = true;
_searchFullMigrated = true; auto list = processList(data.vmessages());
} else {
_searchFull = true;
}
auto list = process(data.vmessages());
fullCount = list.size(); fullCount = list.size();
return list; return list;
}, [&](const MTPDmessages_messagesSlice &data) { }, [&](const MTPDmessages_messagesSlice &data) {
if (_searchRequest != 0) { if (!cacheResults) {
// Don't apply cached data! // Don't apply cached data!
session().data().processUsers(data.vusers()); session().data().processUsers(data.vusers());
session().data().processChats(data.vchats()); session().data().processChats(data.vchats());
} }
auto list = process(data.vmessages()); auto list = processList(data.vmessages());
const auto nextRate = data.vnext_rate(); const auto nextRate = data.vnext_rate();
const auto rateUpdated = nextRate && (nextRate->v != _searchNextRate); const auto rateUpdated = nextRate
const auto finished = (type == SearchRequestType::FromStart || type == SearchRequestType::FromOffset) && (nextRate->v != process->nextRate);
? !rateUpdated const auto finished = (type.peer || type.migrated || type.posts)
: list.empty(); ? list.empty()
: !rateUpdated;
if (rateUpdated) { if (rateUpdated) {
_searchNextRate = nextRate->v; process->nextRate = nextRate->v;
} }
if (finished) { if (finished) {
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { process->full = true;
_searchFullMigrated = true;
} else {
_searchFull = true;
}
} }
fullCount = data.vcount().v; fullCount = data.vcount().v;
return list; return list;
@ -2613,33 +2649,26 @@ void Widget::searchReceived(
"received messages.channelMessages when no channel " "received messages.channelMessages when no channel "
"was passed! (Widget::searchReceived)")); "was passed! (Widget::searchReceived)"));
} }
if (_searchRequest != 0) { if (!cacheResults) {
// Don't apply cached data! // Don't apply cached data!
session().data().processUsers(data.vusers()); session().data().processUsers(data.vusers());
session().data().processChats(data.vchats()); session().data().processChats(data.vchats());
} }
auto list = process(data.vmessages()); auto list = processList(data.vmessages());
if (list.empty()) { if (list.empty()) {
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { process->full = true;
_searchFullMigrated = true;
} else {
_searchFull = true;
}
} }
fullCount = data.vcount().v; fullCount = data.vcount().v;
return list; return list;
}, [&](const MTPDmessages_messagesNotModified &) { }, [&](const MTPDmessages_messagesNotModified &) {
LOG(("API Error: received messages.messagesNotModified! (Widget::searchReceived)")); LOG(("API Error: received messages.messagesNotModified! "
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { "(Widget::searchReceived)"));
_searchFullMigrated = true; process->full = true;
} else {
_searchFull = true;
}
return std::vector<not_null<HistoryItem*>>(); return std::vector<not_null<HistoryItem*>>();
}); });
_inner->searchReceived(messages, inject, type, fullCount); _inner->searchReceived(messages, inject, type, fullCount);
_searchRequest = 0; process->requestId = 0;
listScrollUpdated(); listScrollUpdated();
update(); update();
} }
@ -2671,15 +2700,17 @@ void Widget::peerSearchReceived(
} }
} }
void Widget::searchApplyEmpty(SearchRequestType type, mtpRequestId id) { void Widget::searchApplyEmpty(
_searchFull = _searchFullMigrated = true; SearchRequestType type,
not_null<SearchProcessState*> process) {
process->full = true;
searchReceived( searchReceived(
type, type,
MTP_messages_messages( MTP_messages_messages(
MTP_vector<MTPMessage>(), MTP_vector<MTPMessage>(),
MTP_vector<MTPChat>(), MTP_vector<MTPChat>(),
MTP_vector<MTPUser>()), MTP_vector<MTPUser>()),
id); process);
} }
void Widget::peerSearchApplyEmpty(mtpRequestId id) { void Widget::peerSearchApplyEmpty(mtpRequestId id) {
@ -2696,16 +2727,12 @@ void Widget::peerSearchApplyEmpty(mtpRequestId id) {
void Widget::searchFailed( void Widget::searchFailed(
SearchRequestType type, SearchRequestType type,
const MTP::Error &error, const MTP::Error &error,
mtpRequestId requestId) { not_null<SearchProcessState*> process) {
if (error.type() == u"SEARCH_QUERY_EMPTY"_q) { if (error.type() == u"SEARCH_QUERY_EMPTY"_q) {
searchApplyEmpty(type, requestId); searchApplyEmpty(type, process);
} else if (_searchRequest == requestId) { } else {
_searchRequest = 0; process->requestId = 0;
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { process->full = true;
_searchFullMigrated = true;
} else {
_searchFull = true;
}
} }
} }
@ -2838,6 +2865,7 @@ QString Widget::validateSearchQuery() {
} else { } else {
_searchHashOrCashtag = IsHashOrCashtagSearchQuery(query); _searchHashOrCashtag = IsHashOrCashtagSearchQuery(query);
} }
_searchWithPostsPreview = computeSearchWithPostsPreview();
return query; return query;
} }
@ -3096,6 +3124,7 @@ bool Widget::applySearchState(SearchState state) {
? peer->owner().history(migrateFrom).get() ? peer->owner().history(migrateFrom).get()
: nullptr; : nullptr;
_searchState = state; _searchState = state;
_searchWithPostsPreview = computeSearchWithPostsPreview();
if (queryChanged) { if (queryChanged) {
updateLockUnlockVisibility(anim::type::normal); updateLockUnlockVisibility(anim::type::normal);
updateLoadMoreChatsVisibility(); updateLoadMoreChatsVisibility();
@ -3111,16 +3140,20 @@ bool Widget::applySearchState(SearchState state) {
updateSearchFromVisibility(); updateSearchFromVisibility();
updateLockUnlockPosition(); updateLockUnlockPosition();
if ((state.query.isEmpty() && !state.fromPeer && state.tags.empty()) const auto searchCleared = state.query.isEmpty()
&& !state.fromPeer
&& state.tags.empty();
if (searchCleared
|| inChatChanged || inChatChanged
|| fromPeerChanged || fromPeerChanged
|| tagsChanged || tagsChanged
|| tabChanged) { || tabChanged) {
clearSearchCache(); clearSearchCache(searchCleared);
} }
if (state.query.isEmpty()) { if (state.query.isEmpty()) {
_peerSearchCache.clear(); _peerSearchCache.clear();
for (const auto &[requestId, query] : base::take(_peerSearchQueries)) { const auto queries = base::take(_peerSearchQueries);
for (const auto &[requestId, query] : queries) {
_api.request(requestId).cancel(); _api.request(requestId).cancel();
} }
_peerSearchQuery = QString(); _peerSearchQuery = QString();
@ -3158,15 +3191,23 @@ bool Widget::applySearchState(SearchState state) {
return true; return true;
} }
void Widget::clearSearchCache() { void Widget::clearSearchCache(bool clearPosts) {
_searchCache.clear(); _searchProcess.cache.clear();
_singleMessageSearch.clear(); _singleMessageSearch.clear();
for (const auto &[requestId, query] : base::take(_searchQueries)) { const auto queries = base::take(_searchProcess.queries);
for (const auto &[requestId, query] : queries) {
session().api().request(requestId).cancel(); session().api().request(requestId).cancel();
} }
_searchQuery = QString(); _searchQuery = QString();
_searchQueryFrom = nullptr; _searchQueryFrom = nullptr;
_searchQueryTags.clear(); _searchQueryTags.clear();
if (clearPosts) {
_postsProcess.cache.clear();
const auto queries = base::take(_postsProcess.queries);
for (const auto &[requestId, query] : queries) {
session().api().request(requestId).cancel();
}
}
_topicSearchQuery = QString(); _topicSearchQuery = QString();
_topicSearchOffsetDate = 0; _topicSearchOffsetDate = 0;
_topicSearchOffsetId = _topicSearchOffsetTopicId = 0; _topicSearchOffsetId = _topicSearchOffsetTopicId = 0;
@ -3241,10 +3282,17 @@ void Widget::completeHashtag(QString tag) {
if (cur == start + 1 if (cur == start + 1
|| base::StringViewMid(t, start + 1, cur - start - 1) || base::StringViewMid(t, start + 1, cur - start - 1)
== base::StringViewMid(tag, 0, cur - start - 1)) { == base::StringViewMid(tag, 0, cur - start - 1)) {
for (; cur < t.size() && cur - start - 1 < tag.size(); ++cur) { while (cur < t.size() && cur - start - 1 < tag.size()) {
if (t.at(cur) != tag.at(cur - start - 1)) break; if (t.at(cur) != tag.at(cur - start - 1)) {
break;
}
++cur;
}
if (cur - start - 1 == tag.size()
&& cur < t.size()
&& t.at(cur) == ' ') {
++cur;
} }
if (cur - start - 1 == tag.size() && cur < t.size() && t.at(cur) == ' ') ++cur;
hashtag = t.mid(0, start + 1) + tag + ' ' + t.mid(cur); hashtag = t.mid(0, start + 1) + tag + ' ' + t.mid(cur);
setSearchQuery(hashtag, start + 1 + tag.size() + 1); setSearchQuery(hashtag, start + 1 + tag.size() + 1);
applySearchUpdate(); applySearchUpdate();
@ -3356,7 +3404,8 @@ void Widget::updateControlsGeometry() {
? st::dialogsFilterSkip ? st::dialogsFilterSkip
: (st::dialogsFilterPadding.x() + _mainMenu.toggle->width())) : (st::dialogsFilterPadding.x() + _mainMenu.toggle->width()))
+ st::dialogsFilterPadding.x(); + st::dialogsFilterPadding.x();
const auto filterRight = st::dialogsFilterSkip + st::dialogsFilterPadding.x(); const auto filterRight = st::dialogsFilterSkip
+ st::dialogsFilterPadding.x();
const auto filterWidth = qMax(ratiow, smallw) - filterLeft - filterRight; const auto filterWidth = qMax(ratiow, smallw) - filterLeft - filterRight;
const auto filterAreaHeight = st::topBarHeight; const auto filterAreaHeight = st::topBarHeight;
_searchControls->setGeometry(0, filterAreaTop, ratiow, filterAreaHeight); _searchControls->setGeometry(0, filterAreaTop, ratiow, filterAreaHeight);
@ -3369,7 +3418,11 @@ void Widget::updateControlsGeometry() {
auto filterTop = (filterAreaHeight - _search->height()) / 2; auto filterTop = (filterAreaHeight - _search->height()) / 2;
filterLeft = anim::interpolate(filterLeft, _narrowWidth, narrowRatio); filterLeft = anim::interpolate(filterLeft, _narrowWidth, narrowRatio);
_search->setGeometryToLeft(filterLeft, filterTop, filterWidth, _search->height()); _search->setGeometryToLeft(
filterLeft,
filterTop,
filterWidth,
_search->height());
auto mainMenuLeft = anim::interpolate( auto mainMenuLeft = anim::interpolate(
st::dialogsFilterPadding.x(), st::dialogsFilterPadding.x(),
@ -3387,12 +3440,16 @@ void Widget::updateControlsGeometry() {
-_searchForNarrowLayout->width(), -_searchForNarrowLayout->width(),
(_narrowWidth - _searchForNarrowLayout->width()) / 2, (_narrowWidth - _searchForNarrowLayout->width()) / 2,
narrowRatio); narrowRatio);
_searchForNarrowLayout->moveToLeft(searchLeft, st::dialogsFilterPadding.y()); _searchForNarrowLayout->moveToLeft(
searchLeft,
st::dialogsFilterPadding.y());
auto right = filterLeft + filterWidth; auto right = filterLeft + filterWidth;
_cancelSearch->moveToLeft(right - _cancelSearch->width(), _search->y()); _cancelSearch->moveToLeft(right - _cancelSearch->width(), _search->y());
right -= _jumpToDate->width(); _jumpToDate->moveToLeft(right, _search->y()); right -= _jumpToDate->width();
right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _search->y()); _jumpToDate->moveToLeft(right, _search->y());
right -= _chooseFromUser->width();
_chooseFromUser->moveToLeft(right, _search->y());
const auto barw = width(); const auto barw = width();
const auto expandedStoriesTop = filterAreaTop + filterAreaHeight; const auto expandedStoriesTop = filterAreaTop + filterAreaHeight;
@ -3691,9 +3748,11 @@ void Widget::scrollToEntry(const RowDescriptor &entry) {
} }
void Widget::cancelSearchRequest() { void Widget::cancelSearchRequest() {
session().api().request(base::take(_searchRequest)).cancel(); session().api().request(base::take(_searchProcess.requestId)).cancel();
session().api().request(base::take(_migratedProcess.requestId)).cancel();
session().api().request(base::take(_postsProcess.requestId)).cancel();
session().data().histories().cancelRequest( session().data().histories().cancelRequest(
base::take(_searchInHistoryRequest)); base::take(_historiesRequest));
} }
PeerData *Widget::searchInPeer() const { PeerData *Widget::searchInPeer() const {
@ -3810,8 +3869,12 @@ bool Widget::cancelSearch(CancelSearchOptions options) {
// Don't create suggestions in unfocus case. // Don't create suggestions in unfocus case.
setInnerFocus(true); setInnerFocus(true);
} }
_lastSearchPeer = nullptr; _searchProcess.lastPeer = nullptr;
_lastSearchId = _lastSearchMigratedId = 0; _searchProcess.lastId = 0;
_migratedProcess.lastPeer = nullptr;
_migratedProcess.lastId = 0;
_postsProcess.lastPeer = nullptr;
_postsProcess.lastId = 0;
_inner->clearFilter(); _inner->clearFilter();
applySearchState(std::move(updatedState)); applySearchState(std::move(updatedState));
if (_suggestions && clearSearchFocus) { if (_suggestions && clearSearchFocus) {

View file

@ -75,7 +75,7 @@ class FakeRow;
class Key; class Key;
struct ChosenRow; struct ChosenRow;
class InnerWidget; class InnerWidget;
enum class SearchRequestType : uchar; struct SearchRequestType;
enum class SearchRequestDelay : uchar; enum class SearchRequestDelay : uchar;
class Suggestions; class Suggestions;
class ChatSearchIn; class ChatSearchIn;
@ -151,10 +151,26 @@ protected:
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
private: private:
struct SearchProcessState {
base::flat_map<QString, MTPmessages_Messages> cache;
base::flat_map<mtpRequestId, QString> queries;
PeerData *lastPeer = nullptr;
MsgId lastId = 0;
int32 nextRate = 0;
mtpRequestId requestId = 0;
bool full = false;
};
void chosenRow(const ChosenRow &row); void chosenRow(const ChosenRow &row);
void listScrollUpdated(); void listScrollUpdated();
void searchCursorMoved(); void searchCursorMoved();
void completeHashtag(QString tag); void completeHashtag(QString tag);
void requestPublicPosts(bool fromStart);
void requestMessages(bool fromStart);
[[nodiscard]] not_null<SearchProcessState*> currentSearchProcess();
[[nodiscard]] bool computeSearchWithPostsPreview() const;
[[nodiscard]] QString currentSearchQuery() const; [[nodiscard]] QString currentSearchQuery() const;
[[nodiscard]] int currentSearchQueryCursorPosition() const; [[nodiscard]] int currentSearchQueryCursorPosition() const;
@ -168,7 +184,8 @@ private:
void searchReceived( void searchReceived(
SearchRequestType type, SearchRequestType type,
const MTPmessages_Messages &result, const MTPmessages_Messages &result,
mtpRequestId requestId); not_null<SearchProcessState*> process,
bool cacheResults = false);
void peerSearchReceived( void peerSearchReceived(
const MTPcontacts_Found &result, const MTPcontacts_Found &result,
mtpRequestId requestId); mtpRequestId requestId);
@ -201,7 +218,7 @@ private:
void showCalendar(); void showCalendar();
void showSearchFrom(); void showSearchFrom();
void showMainMenu(); void showMainMenu();
void clearSearchCache(); void clearSearchCache(bool clearPosts);
void setSearchQuery(const QString &query, int cursorPosition = -1); void setSearchQuery(const QString &query, int cursorPosition = -1);
void updateControlsVisibility(bool fast = false); void updateControlsVisibility(bool fast = false);
void updateLockUnlockVisibility( void updateLockUnlockVisibility(
@ -244,9 +261,11 @@ private:
void searchFailed( void searchFailed(
SearchRequestType type, SearchRequestType type,
const MTP::Error &error, const MTP::Error &error,
mtpRequestId requestId); not_null<SearchProcessState*> process);
void peerSearchFailed(const MTP::Error &error, mtpRequestId requestId); void peerSearchFailed(const MTP::Error &error, mtpRequestId requestId);
void searchApplyEmpty(SearchRequestType type, mtpRequestId id); void searchApplyEmpty(
SearchRequestType type,
not_null<SearchProcessState*> process);
void peerSearchApplyEmpty(mtpRequestId id); void peerSearchApplyEmpty(mtpRequestId id);
void updateForceDisplayWide(); void updateForceDisplayWide();
@ -318,6 +337,7 @@ private:
bool _scrollToTopIsShown = false; bool _scrollToTopIsShown = false;
bool _forumSearchRequested = false; bool _forumSearchRequested = false;
HashOrCashtag _searchHashOrCashtag = {}; HashOrCashtag _searchHashOrCashtag = {};
bool _searchWithPostsPreview = false;
Data::Folder *_openedFolder = nullptr; Data::Folder *_openedFolder = nullptr;
Data::Forum *_openedForum = nullptr; Data::Forum *_openedForum = nullptr;
@ -358,19 +378,13 @@ private:
PeerData *_searchQueryFrom = nullptr; PeerData *_searchQueryFrom = nullptr;
std::vector<Data::ReactionId> _searchQueryTags; std::vector<Data::ReactionId> _searchQueryTags;
ChatSearchTab _searchQueryTab = {}; ChatSearchTab _searchQueryTab = {};
int32 _searchNextRate = 0;
bool _searchFull = false;
bool _searchFullMigrated = false;
int _searchInHistoryRequest = 0; // Not real mtpRequestId.
mtpRequestId _searchRequest = 0;
PeerData *_lastSearchPeer = nullptr; SearchProcessState _searchProcess;
MsgId _lastSearchId = 0; SearchProcessState _migratedProcess;
MsgId _lastSearchMigratedId = 0; SearchProcessState _postsProcess;
int _historiesRequest = 0; // Not real mtpRequestId.
base::flat_map<QString, MTPmessages_Messages> _searchCache;
Api::SingleMessageSearch _singleMessageSearch; Api::SingleMessageSearch _singleMessageSearch;
base::flat_map<mtpRequestId, QString> _searchQueries;
base::flat_map<QString, MTPcontacts_Found> _peerSearchCache; base::flat_map<QString, MTPcontacts_Found> _peerSearchCache;
base::flat_map<mtpRequestId, QString> _peerSearchQueries; base::flat_map<mtpRequestId, QString> _peerSearchQueries;