diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index ab6577613..7d63bb597 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -88,6 +88,7 @@ namespace { constexpr auto kHashtagResultsLimit = 5; constexpr auto kStartReorderThreshold = 30; constexpr auto kQueryPreviewLimit = 32; +constexpr auto kPreviewPostsLimit = 3; [[nodiscard]] int FixedOnTopDialogsCount(not_null list) { auto result = 0; @@ -555,7 +556,7 @@ int InnerWidget::searchInChatSkip() const { return _searchIn ? _searchIn->height() : 0; } -int InnerWidget::searchedOffset() const { +int InnerWidget::previewOffset() const { auto result = peerSearchOffset(); if (!_peerSearchResults.empty()) { result += (_peerSearchResults.size() * st::dialogsRowHeight) @@ -564,6 +565,15 @@ int InnerWidget::searchedOffset() const { 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) { Expects(!folder || !_savedSublists); @@ -810,7 +820,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } if (_searchIn) { p.translate(0, searchInChatSkip()); - if (_searchResults.empty()) { + if (_previewResults.empty() && _searchResults.empty()) { p.fillRect(0, 0, fullWidth, st::lineWidth, st::shadowFg); } } @@ -924,7 +934,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } const auto showUnreadInSearchResults = uniqueSearchResults(); - if (_searchResults.empty()) { + if (_previewResults.empty() && _searchResults.empty()) { if (_loadingAnimation) { const auto text = tr::lng_contacts_loading(tr::now); 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.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 ? u"Search results"_q : tr::lng_search_found_results( @@ -1330,6 +1401,8 @@ void InnerWidget::clearIrrelevantState() { setFilteredPressed(-1, false); _peerSearchSelected = -1; setPeerSearchPressed(-1); + _previewSelected = -1; + setPreviewPressed(-1); _searchedSelected = -1; setSearchedPressed(-1); } else if (_state == WidgetState::Filtered) { @@ -1446,6 +1519,32 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { 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()) { auto skip = searchedOffset(); auto searchedSelected = (mouseY >= skip) ? ((mouseY - skip) / _st->height) : -1; @@ -1495,7 +1594,9 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { _hashtagDeletePressed = _hashtagDeleteSelected; setFilteredPressed(_filteredSelected, _selectedTopicJump); setPeerSearchPressed(_peerSearchSelected); + setPreviewPressed(_previewSelected); setSearchedPressed(_searchedSelected); + _pressedMorePosts = _selectedMorePosts; const auto alt = (e->modifiers() & Qt::AltModifier); if (alt && showChatPreview()) { @@ -1862,8 +1963,12 @@ void InnerWidget::mousePressReleased( setFilteredPressed(-1, false); auto peerSearchPressed = _peerSearchPressed; setPeerSearchPressed(-1); + auto previewPressed = _previewPressed; + setPreviewPressed(-1); auto searchedPressed = _searchedPressed; setSearchedPressed(-1); + const auto pressedMorePosts = _pressedMorePosts; + _pressedMorePosts = false; if (wasDragging) { selectByMouse(globalPosition); } @@ -1879,8 +1984,12 @@ void InnerWidget::mousePressReleased( || (filteredPressed >= 0 && filteredPressed == _filteredSelected) || (peerSearchPressed >= 0 && peerSearchPressed == _peerSearchSelected) + || (previewPressed >= 0 + && previewPressed == _previewSelected) || (searchedPressed >= 0 - && searchedPressed == _searchedSelected)) { + && searchedPressed == _searchedSelected) + || (pressedMorePosts + && pressedMorePosts == _selectedMorePosts)) { chooseRow(modifiers, pressedTopicRootId); } } @@ -1955,6 +2064,13 @@ void InnerWidget::setPeerSearchPressed(int 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) { if (base::in_range(_searchedPressed, 0, _searchResults.size())) { _searchResults[_searchedPressed]->stopLastRipple(); @@ -2313,6 +2429,8 @@ void InnerWidget::updateSelectedRow(Key key) { } } else if (_peerSearchSelected >= 0) { 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) { update(0, searchedOffset() + _searchedSelected * _st->height, width(), _st->height); } @@ -2354,9 +2472,11 @@ void InnerWidget::clearSelection() { if (isSelected()) { updateSelectedRow(); _collapsedSelected = -1; + _selectedMorePosts = false; _selected = nullptr; _filteredSelected = _searchedSelected + = _previewSelected = _peerSearchSelected = _hashtagSelected = -1; @@ -2449,6 +2569,11 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { } else if (_state == WidgetState::Filtered) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) { 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())) { return { _searchResults[_searchedSelected]->item()->history(), @@ -2597,6 +2722,7 @@ void InnerWidget::searchRequested(bool loading) { _searchLoading = loading; if (loading) { clearSearchResults(true); + clearPreviewResults(); } refresh(true); } @@ -2660,6 +2786,7 @@ void InnerWidget::applySearchState(SearchState state) { } _searchState = std::move(state); _searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query); + _searchWithPostsPreview = computeSearchWithPostsPreview(); updateSearchIn(); moveSearchIn(); @@ -2767,7 +2894,7 @@ void InnerWidget::appendToFiltered(Key key) { const auto height = filteredHeight(); _filterResults.emplace_back(i->second.get()); _filterResults.back().top = height; - trackSearchResultsHistory(key.owningHistory()); + trackResultsHistory(key.owningHistory()); } InnerWidget::~InnerWidget() { @@ -2780,13 +2907,16 @@ void InnerWidget::clearSearchResults(bool clearPeerSearchResults) { _peerSearchResults.clear(); } _searchResults.clear(); - _searchResultsLifetime.destroy(); - _searchResultsHistories.clear(); _searchedCount = _searchedMigratedCount = 0; } -void InnerWidget::trackSearchResultsHistory(not_null history) { - if (!_searchResultsHistories.emplace(history).second) { +void InnerWidget::clearPreviewResults() { + _previewResults.clear(); + _previewCount = 0; +} + +void InnerWidget::trackResultsHistory(not_null history) { + if (!_trackedHistories.emplace(history).second) { return; } const auto channel = history->peer->asChannel(); @@ -2827,7 +2957,7 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { clearMouseSelection(true); } update(); - }, _searchResultsLifetime); + }, _trackedLifetime); if (const auto forum = channel->forum()) { forum->topicDestroyed( @@ -2857,7 +2987,7 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { if (_chatPreviewRow.key.topic() == topic) { _chatPreviewRow = {}; } - }, _searchResultsLifetime); + }, _trackedLifetime); } } @@ -2875,6 +3005,13 @@ Data::Thread *InnerWidget::updateFromParentDrag(QPoint globalPosition) { } else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) { return session().data().history( _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())) { if (const auto item = _searchResults[_searchedSelected]->item()) { if (const auto topic = item->topic()) { @@ -3028,12 +3165,14 @@ void InnerWidget::searchReceived( _searchLoading = false; const auto uniquePeers = uniqueSearchResults(); - if (type == SearchRequestType::FromStart - || type == SearchRequestType::PeerFromStart) { + const auto withPreview = _searchWithPostsPreview; + const auto toPreview = withPreview && type.posts; + if (type.start && !type.migrated && (!withPreview || !type.posts)) { clearSearchResults(false); } - const auto isMigratedSearch = (type == SearchRequestType::MigratedFromStart) - || (type == SearchRequestType::MigratedFromOffset); + if (!withPreview || toPreview) { + clearPreviewResults(); + } const auto key = (!_openedForum || _searchState.inChat.topic()) ? _searchState.inChat @@ -3042,34 +3181,40 @@ void InnerWidget::searchReceived( && (!_searchState.inChat || inject->history() == _searchState.inChat.history())) { Assert(_searchResults.empty()); + Assert(!toPreview); const auto index = int(_searchResults.size()); _searchResults.push_back( std::make_unique( key, inject, [=] { repaintSearchResult(index); })); - trackSearchResultsHistory(inject->history()); + trackResultsHistory(inject->history()); ++fullCount; } + auto &results = toPreview ? _previewResults : _searchResults; for (const auto &item : messages) { const auto history = item->history(); - if (!uniquePeers || !hasHistoryInResults(history)) { - const auto index = int(_searchResults.size()); - _searchResults.push_back( - std::make_unique( - key, - item, - [=] { repaintSearchResult(index); })); - trackSearchResultsHistory(history); - if (uniquePeers && !history->unreadCountKnown()) { + if (toPreview || !uniquePeers || !hasHistoryInResults(history)) { + const auto index = int(results.size()); + const auto repaint = toPreview + ? Fn([=] { repaintSearchResult(index); }) + : [=] { repaintPreviewResult(index); }; + results.push_back( + std::make_unique(key, item, repaint)); + trackResultsHistory(history); + if (!toPreview && uniquePeers && !history->unreadCountKnown()) { history->owner().histories().requestDialogEntry(history); + } else if (toPreview && results.size() >= kPreviewPostsLimit) { + break; } } } - if (isMigratedSearch) { + if (type.migrated) { _searchedMigratedCount = fullCount; - } else { + } else if (!withPreview || !toPreview) { _searchedCount = fullCount; + } else { + _previewCount = fullCount; } refresh(); @@ -3313,6 +3458,7 @@ void InnerWidget::clearMouseSelection(bool clearSelection) { } else if (_state == WidgetState::Filtered) { _filteredSelected = _peerSearchSelected + = _previewSelected = _searchedSelected = _hashtagSelected = -1; } @@ -3416,6 +3562,19 @@ void InnerWidget::repaintSearchResult(int index) { _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() { if (_state == WidgetState::Filtered || _searchState.inChat) { if (_searchState.inChat) { @@ -3428,6 +3587,9 @@ void InnerWidget::clearFilter() { _filterResultsGlobal.clear(); _peerSearchResults.clear(); _searchResults.clear(); + _previewResults.clear(); + _trackedHistories.clear(); + _trackedLifetime.destroy(); _filter = QString(); refresh(true); } @@ -3474,15 +3636,22 @@ void InnerWidget::selectSkip(int32 direction) { } scrollToDefaultSelected(); } 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; } - if ((_hashtagSelected < 0 || _hashtagSelected >= _hashtagResults.size()) && - (_filteredSelected < 0 || _filteredSelected >= _filterResults.size()) && - (_peerSearchSelected < 0 || _peerSearchSelected >= _peerSearchResults.size()) && - (_searchedSelected < 0 || _searchedSelected >= _searchResults.size())) { - if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty()) { + if ((_hashtagSelected < 0 || _hashtagSelected >= _hashtagResults.size()) + && (_filteredSelected < 0 || _filteredSelected >= _filterResults.size()) + && (_peerSearchSelected < 0 || _peerSearchSelected >= _peerSearchResults.size()) + && (_previewSelected < 0 || _previewSelected >= _previewResults.size()) + && (_searchedSelected < 0 || _searchedSelected >= _searchResults.size())) { + if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty() && _previewResults.empty()) { _searchedSelected = 0; + } else if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty()) { + _previewSelected = 0; } else if (_hashtagResults.empty() && _filterResults.empty()) { _peerSearchSelected = 0; } else if (_hashtagResults.empty()) { @@ -3493,30 +3662,36 @@ void InnerWidget::selectSkip(int32 direction) { } else { int32 cur = base::in_range(_hashtagSelected, 0, _hashtagResults.size()) ? _hashtagSelected - : (base::in_range(_filteredSelected, 0, _filterResults.size()) - ? (_hashtagResults.size() + _filteredSelected) - : (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size()) - ? (_peerSearchSelected + _filterResults.size() + _hashtagResults.size()) - : (_searchedSelected + _peerSearchResults.size() + _filterResults.size() + _hashtagResults.size()))); + : base::in_range(_filteredSelected, 0, _filterResults.size()) + ? (_hashtagResults.size() + _filteredSelected) + : base::in_range(_peerSearchSelected, 0, _peerSearchResults.size()) + ? (_peerSearchSelected + _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 + direction, 0, static_cast(_hashtagResults.size() + _filterResults.size() + _peerSearchResults.size() + + _previewResults.size() + _searchResults.size()) - 1); if (cur < _hashtagResults.size()) { _hashtagSelected = cur; - _filteredSelected = _peerSearchSelected = _searchedSelected = -1; + _filteredSelected = _peerSearchSelected = _previewSelected = _searchedSelected = -1; } else if (cur < _hashtagResults.size() + _filterResults.size()) { _filteredSelected = cur - _hashtagResults.size(); - _hashtagSelected = _peerSearchSelected = _searchedSelected = -1; + _hashtagSelected = _peerSearchSelected = _previewSelected = _searchedSelected = -1; } else if (cur < _hashtagResults.size() + _filterResults.size() + _peerSearchResults.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 { - _hashtagSelected = _filteredSelected = _peerSearchSelected = -1; - _searchedSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size(); + _searchedSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size() - _previewResults.size(); + _hashtagSelected = _filteredSelected = _peerSearchSelected = _previewSelected = -1; } } if (base::in_range(_hashtagSelected, 0, _hashtagResults.size())) { @@ -3533,6 +3708,13 @@ void InnerWidget::selectSkip(int32 direction) { const auto height = st::dialogsRowHeight + (_peerSearchSelected ? 0 : st::searchedBarHeight); 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 { const auto from = searchedOffset() + _searchedSelected * _st->height @@ -3551,7 +3733,14 @@ void InnerWidget::scrollToEntry(const RowDescriptor &entry) { scrollToItem(dialogsOffset() + row->top(), row->height()); } } 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)) { const auto from = searchedOffset() + i * _st->height; scrollToItem(from, _st->height); @@ -3646,33 +3835,45 @@ void InnerWidget::preloadRowsData() { } yTo -= otherStart; } else if (_state == WidgetState::Filtered) { - int32 from = (yFrom - filteredOffset()) / _st->height; + auto from = (yFrom - filteredOffset()) / _st->height; if (from < 0) from = 0; if (from < _filterResults.size()) { - int32 to = (yTo / _st->height) + 1; - if (to > _filterResults.size()) to = _filterResults.size(); - + const auto to = std::min( + ((yTo - filteredOffset()) / _st->height) + 1, + int(_filterResults.size())); for (; from < to; ++from) { _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 < _peerSearchResults.size()) { - int32 to = (yTo > filteredOffset() + st::searchedBarHeight ? ((yTo - filteredOffset() - st::searchedBarHeight) / st::dialogsRowHeight) : 0) - _filterResults.size() + 1; - if (to > _peerSearchResults.size()) to = _peerSearchResults.size(); - + const auto to = std::min( + ((yTo - peerSearchOffset()) / st::dialogsRowHeight) + 1, + int(_peerSearchResults.size())); for (; from < to; ++from) { _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 < _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; - if (to > _searchResults.size()) to = _searchResults.size(); - + const auto to = std::min( + ((yTo - searchedOffset()) / _st->height) + 1, + int(_searchResults.size())); for (; from < to; ++from) { _searchResults[from]->item()->history()->peer->loadUserpic(); } @@ -3803,6 +4004,14 @@ ChosenRow InnerWidget::computeChosenRow() const { .key = session().data().history(peer), .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())) { const auto result = _searchResults[_searchedSelected].get(); const auto topic = result->topic(); @@ -3832,6 +4041,11 @@ bool InnerWidget::chooseRow( MsgId pressedTopicRootId) { if (chooseHashtag()) { return true; + } else if (_selectedMorePosts) { + if (_searchHashOrCashtag != HashOrCashtag::None) { + _changeSearchTabRequests.fire(ChatSearchTab::PublicPosts); + } + return true; } const auto modifyChosenRow = [&]( ChosenRow row, diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index a0b80ef67..a284cca11 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -72,13 +72,18 @@ struct ChosenRow { bool newWindow : 1 = false; }; -enum class SearchRequestType : uchar { - FromStart, - FromOffset, - PeerFromStart, - PeerFromOffset, - MigratedFromStart, - MigratedFromOffset, +struct SearchRequestType { + bool migrated : 1 = false; + bool posts : 1 = false; + bool start : 1 = false; + bool peer : 1 = false; + + friend inline constexpr auto operator<=>( + SearchRequestType a, + SearchRequestType b) = default; + friend inline constexpr bool operator==( + SearchRequestType a, + SearchRequestType b) = default; }; enum class SearchRequestDelay : uchar { @@ -283,6 +288,7 @@ private: void setHashtagPressed(int pressed); void setFilteredPressed(int pressed, bool pressedTopicJump); void setPeerSearchPressed(int pressed); + void setPreviewPressed(int pressed); void setSearchedPressed(int pressed); bool isPressed() const { return (_collapsedPressed >= 0) @@ -290,7 +296,9 @@ private: || (_hashtagPressed >= 0) || (_filteredPressed >= 0) || (_peerSearchPressed >= 0) - || (_searchedPressed >= 0); + || (_previewPressed >= 0) + || (_searchedPressed >= 0) + || _pressedMorePosts; } bool isSelected() const { return (_collapsedSelected >= 0) @@ -298,7 +306,9 @@ private: || (_hashtagSelected >= 0) || (_filteredSelected >= 0) || (_peerSearchSelected >= 0) - || (_searchedSelected >= 0); + || (_previewSelected >= 0) + || (_searchedSelected >= 0) + || _selectedMorePosts; } bool uniqueSearchResults() const; bool hasHistoryInResults(not_null history) const; @@ -352,6 +362,7 @@ private: [[nodiscard]] int filteredHeight(int till = -1) const; [[nodiscard]] int peerSearchOffset() const; [[nodiscard]] int searchInChatOffset() const; + [[nodiscard]] int previewOffset() const; [[nodiscard]] int searchedOffset() const; [[nodiscard]] int searchInChatSkip() const; [[nodiscard]] int hashtagsOffset() const; @@ -403,14 +414,18 @@ private: // const Ui::Text::String &text) const; void updateSearchIn(); void repaintSearchResult(int index); + void repaintPreviewResult(int index); + + [[nodiscard]] bool computeSearchWithPostsPreview() const; Ui::VideoUserpic *validateVideoUserpic(not_null row); Ui::VideoUserpic *validateVideoUserpic(not_null history); Row *shownRowByKey(Key key); void clearSearchResults(bool clearPeerSearchResults = true); + void clearPreviewResults(); void updateSelectedRow(Key key = Key()); - void trackSearchResultsHistory(not_null history); + void trackResultsHistory(not_null history); [[nodiscard]] QBrush currentBg() const; [[nodiscard]] RowDescriptor computeChatPreviewRow() const; @@ -449,6 +464,8 @@ private: std::vector> _collapsedRows; not_null _st; mutable std::unique_ptr _topicJumpCache; + bool _selectedMorePosts = false; + bool _pressedMorePosts = false; int _collapsedSelected = -1; int _collapsedPressed = -1; bool _skipTopDialog = false; @@ -487,14 +504,21 @@ private: EmptyState _emptyState = EmptyState::None; + base::flat_set> _trackedHistories; + rpl::lifetime _trackedLifetime; + QString _peerSearchQuery; std::vector> _peerSearchResults; int _peerSearchSelected = -1; int _peerSearchPressed = -1; + std::vector> _previewResults; + int _previewCount = 0; + int _previewSelected = -1; + int _previewPressed = -1; + int _morePostsWidth = 0; + std::vector> _searchResults; - base::flat_set> _searchResultsHistories; - rpl::lifetime _searchResultsLifetime; int _searchedCount = 0; int _searchedMigratedCount = 0; int _searchedSelected = -1; @@ -516,6 +540,7 @@ private: SearchState _searchState; HashOrCashtag _searchHashOrCashtag = {}; + bool _searchWithPostsPreview = false; History *_searchInMigrated = nullptr; PeerData *_searchFromShown = nullptr; Ui::Text::String _searchFromUserText; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 3d9d1daf4..9cef777db 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -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); if ((was & StateFlag::Disabled) != (state() & StateFlag::Disabled)) { _loading = isDisabled() @@ -532,11 +534,11 @@ Widget::Widget( }, lifetime()); } - _cancelSearch->setClickedCallback([this] { + _cancelSearch->setClickedCallback([=] { cancelSearch({ .jumpBackToSearchedChat = true }); }); - _jumpToDate->entity()->setClickedCallback([this] { showCalendar(); }); - _chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); }); + _jumpToDate->entity()->setClickedCallback([=] { showCalendar(); }); + _chooseFromUser->entity()->setClickedCallback([=] { showSearchFrom(); }); rpl::single(rpl::empty) | rpl::then( session().domain().local().localPasscodeChanged() ) | rpl::start_with_next([=] { @@ -576,11 +578,10 @@ Widget::Widget( }); _inner->setLoadMoreCallback([=] { const auto state = _inner->state(); + const auto process = currentSearchProcess(); if (state == WidgetState::Filtered - && (!_searchFull - || (_searchInMigrated - && _searchFull - && !_searchFullMigrated))) { + && (!process->full + || (_searchInMigrated && !_migratedProcess.full))) { searchMore(); } else if (_openedForum && state == WidgetState::Default) { _openedForum->requestTopics(); @@ -1199,11 +1200,12 @@ void Widget::fullSearchRefreshOn(rpl::producer<> events) { return !_searchQuery.isEmpty(); }) | rpl::start_with_next([=] { _searchTimer.cancel(); - _searchCache.clear(); - _singleMessageSearch.clear(); - for (const auto &[requestId, query] : base::take(_searchQueries)) { + _searchProcess.cache.clear(); + const auto queries = base::take(_searchProcess.queries); + for (const auto &[requestId, query] : queries) { session().api().request(requestId).cancel(); } + _singleMessageSearch.clear(); _searchQuery = QString(); _scroll->scrollToY(0); cancelSearchRequest(); @@ -1529,6 +1531,7 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) { _searchState.tab = forum ? ChatSearchTab::ThisPeer : ChatSearchTab::MyMessages; + _searchWithPostsPreview = computeSearchWithPostsPreview(); _api.request(base::take(_topicSearchRequest)).cancel(); _inner->changeOpenedForum(forum); storiesToggleExplicitExpand(false); @@ -2110,12 +2113,18 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { const auto fromPeer = searchFromPeer(); const auto &inTags = searchInTags(); const auto tab = _searchState.tab; - const auto fromStartType = inPeer - ? SearchRequestType::PeerFromStart - : SearchRequestType::FromStart; + const auto fromStartType = SearchRequestType{ + .start = true, + .peer = (inPeer != nullptr), + }; if (trimmed.isEmpty() && !fromPeer && inTags.empty()) { cancelSearchRequest(); - searchApplyEmpty(fromStartType, 0); + searchApplyEmpty(fromStartType, currentSearchProcess()); + if (_searchWithPostsPreview) { + searchApplyEmpty( + { .posts = true, .start = true }, + &_postsProcess); + } _api.request(base::take(_peerSearchRequest)).cancel(); _peerSearchQuery = QString(); peerSearchApplyEmpty(0); @@ -2128,28 +2137,32 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { if (!success) { return false; } - const auto i = _searchCache.find(query); - if (i != _searchCache.end()) { + const auto process = currentSearchProcess(); + const auto i = process->cache.find(query); + if (i != process->cache.end()) { _searchQuery = query; _searchQueryFrom = fromPeer; _searchQueryTags = inTags; _searchQueryTab = tab; - _searchNextRate = 0; - _searchFull = _searchFullMigrated = false; + process->nextRate = 0; + process->full = false; + _migratedProcess.full = false; cancelSearchRequest(); - searchReceived(fromStartType, i->second, 0); + searchReceived(fromStartType, i->second, process, true); result = true; } } else if (_searchQuery != query || _searchQueryFrom != fromPeer || _searchQueryTags != inTags || _searchQueryTab != tab) { + const auto process = currentSearchProcess(); _searchQuery = query; _searchQueryFrom = fromPeer; _searchQueryTags = inTags; _searchQueryTab = tab; - _searchNextRate = 0; - _searchFull = _searchFullMigrated = false; + process->nextRate = 0; + process->full = false; + _migratedProcess.full = false; cancelSearchRequest(); if (inPeer) { const auto topic = searchInTopic(); @@ -2163,83 +2176,55 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { const auto savedPeer = sublist ? sublist->peer().get() : nullptr; - _searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn finish) { - const auto type = SearchRequestType::PeerFromStart; + _historiesRequest = histories.sendRequest(history, type, [=]( + Fn finish) { + const auto type = SearchRequestType{ + .start = true, + .peer = true, + }; using Flag = MTPmessages_Search::Flag; - _searchRequest = session().api().request(MTPmessages_Search( - MTP_flags((topic ? Flag::f_top_msg_id : Flag()) - | (fromPeer ? Flag::f_from_id : Flag()) - | (savedPeer ? Flag::f_saved_peer_id : Flag()) - | (_searchQueryTags.empty() - ? Flag() - : Flag::f_saved_reaction)), - inPeer->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(0), // offset_id - 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) { - _searchInHistoryRequest = 0; - searchReceived(type, result, _searchRequest); + process->requestId = session().api().request( + MTPmessages_Search( + MTP_flags((topic ? Flag::f_top_msg_id : Flag()) + | (fromPeer ? Flag::f_from_id : Flag()) + | (savedPeer ? Flag::f_saved_peer_id : Flag()) + | (_searchQueryTags.empty() + ? Flag() + : Flag::f_saved_reaction)), + inPeer->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(0), // offset_id + 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) { + _historiesRequest = 0; + searchReceived(type, result, process); finish(); }).fail([=](const MTP::Error &error) { - _searchInHistoryRequest = 0; - searchFailed(type, error, _searchRequest); + _historiesRequest = 0; + searchFailed(type, error, process); finish(); }).send(); - _searchQueries.emplace(_searchRequest, _searchQuery); - return _searchRequest; + process->queries.emplace(process->requestId, _searchQuery); + return process->requestId; }); } else if (_searchState.tab == ChatSearchTab::PublicPosts) { - const auto type = SearchRequestType::FromStart; - _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); + requestPublicPosts(true); } else { - const auto type = 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(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); + requestMessages(true); } _inner->searchRequested(true); } else { @@ -2261,7 +2246,9 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { _peerSearchRequest = _api.request(MTPcontacts_Search( MTP_string(_peerSearchQuery), MTP_int(SearchPeopleLimit) - )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { + )).done([=]( + const MTPcontacts_Found &result, + mtpRequestId requestId) { peerSearchReceived(result, requestId); }).fail([=](const MTP::Error &error, mtpRequestId requestId) { peerSearchFailed(error, requestId); @@ -2368,11 +2355,12 @@ void Widget::searchTopics() { } void Widget::searchMore() { - if (_searchRequest - || _searchInHistoryRequest + const auto process = currentSearchProcess(); + if (process->requestId + || _historiesRequest || _searchTimer.isActive()) { return; - } else if (!_searchFull) { + } else if (!process->full) { if (const auto peer = searchInPeer()) { auto &histories = session().data().histories(); const auto topic = searchInTopic(); @@ -2385,154 +2373,213 @@ void Widget::searchMore() { const auto savedPeer = sublist ? sublist->peer().get() : nullptr; - _searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn finish) { - const auto type = _lastSearchId - ? SearchRequestType::PeerFromOffset - : SearchRequestType::PeerFromStart; + _historiesRequest = histories.sendRequest(history, type, [=]( + Fn finish) { + const auto type = SearchRequestType{ + .start = !process->lastId, + .peer = true, + }; using Flag = MTPmessages_Search::Flag; - _searchRequest = session().api().request(MTPmessages_Search( - MTP_flags((topic ? Flag::f_top_msg_id : Flag()) - | (fromPeer ? Flag::f_from_id : Flag()) - | (savedPeer ? Flag::f_saved_peer_id : Flag()) - | (_searchQueryTags.empty() - ? Flag() - : Flag::f_saved_reaction)), - peer->input, + process->requestId = session().api().request( + MTPmessages_Search( + MTP_flags((topic ? Flag::f_top_msg_id : Flag()) + | (fromPeer ? Flag::f_from_id : Flag()) + | (savedPeer ? Flag::f_saved_peer_id : Flag()) + | (_searchQueryTags.empty() + ? Flag() + : 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 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), - (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), + (_searchQueryFrom + ? _searchQueryFrom->input + : MTP_inputPeerEmpty()), + MTPInputPeer(), // saved_peer_id + MTPVector(), // saved_reaction + MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date MTP_int(0), // max_date - MTP_int(_lastSearchId), + MTP_int(_migratedProcess.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, _searchRequest); - _searchInHistoryRequest = 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 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(), // 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; + MTP_long(0)) // hash + ).done([=](const MTPmessages_Messages &result) { + searchReceived(type, result, &_migratedProcess); + _historiesRequest = 0; finish(); }).fail([=](const MTP::Error &error) { - searchFailed(type, error, _searchRequest); - _searchInHistoryRequest = 0; + searchFailed(type, error, &_migratedProcess); + _historiesRequest = 0; finish(); }).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 { + return (_searchState.tab == ChatSearchTab::PublicPosts) + ? &_postsProcess + : &_searchProcess; +} + +bool Widget::computeSearchWithPostsPreview() const { + return (_searchHashOrCashtag != HashOrCashtag::None) + && (_searchState.tab == ChatSearchTab::MyMessages); +} + void Widget::searchReceived( SearchRequestType type, const MTPmessages_Messages &result, - mtpRequestId requestId) { + not_null process, + bool cacheResults) { const auto state = _inner->state(); - if (state == WidgetState::Filtered) { - if (type == SearchRequestType::FromStart || type == SearchRequestType::PeerFromStart) { - auto i = _searchQueries.find(requestId); - if (i != _searchQueries.end()) { - _searchCache[i->second] = result; - _searchQueries.erase(i); - } + if (!cacheResults + && (state == WidgetState::Filtered) + && type.start) { + const auto i = process->queries.find(process->requestId); + if (i != process->queries.end()) { + process->cache[i->second] = result; + process->queries.erase(i); } } - const auto inject = (type == SearchRequestType::FromStart - || type == SearchRequestType::PeerFromStart) + const auto inject = (type.start && !type.posts) ? *_singleMessageSearch.lookup(_searchQuery) : nullptr; - - if (_searchRequest != requestId) { + if (cacheResults && process->requestId) { return; } - if (type == SearchRequestType::FromStart - || type == SearchRequestType::PeerFromStart) { - _lastSearchPeer = nullptr; - _lastSearchId = _lastSearchMigratedId = 0; + if (type.start) { + process->lastPeer = nullptr; + process->lastId = 0; } - const auto isMigratedSearch = (type == SearchRequestType::MigratedFromStart) - || (type == SearchRequestType::MigratedFromOffset); - const auto process = [&](const MTPVector &messages) { + const auto processList = [&](const MTPVector &messages) { auto result = std::vector>(); for (const auto &message : messages.v) { const auto msgId = IdFromMessage(message); @@ -2546,55 +2593,44 @@ void Widget::searchReceived( NewMessageType::Existing); result.push_back(item); } - _lastSearchPeer = peer; + process->lastPeer = peer; } else { LOG(("API Error: a search results with not loaded peer %1" ).arg(peerId.value)); } - if (isMigratedSearch) { - _lastSearchMigratedId = msgId; - } else { - _lastSearchId = msgId; - } + process->lastId = msgId; } return result; }; auto fullCount = 0; auto messages = result.match([&](const MTPDmessages_messages &data) { - if (_searchRequest != 0) { + if (!cacheResults) { // Don't apply cached data! session().data().processUsers(data.vusers()); session().data().processChats(data.vchats()); } - if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { - _searchFullMigrated = true; - } else { - _searchFull = true; - } - auto list = process(data.vmessages()); + process->full = true; + auto list = processList(data.vmessages()); fullCount = list.size(); return list; }, [&](const MTPDmessages_messagesSlice &data) { - if (_searchRequest != 0) { + if (!cacheResults) { // Don't apply cached data! session().data().processUsers(data.vusers()); session().data().processChats(data.vchats()); } - auto list = process(data.vmessages()); + auto list = processList(data.vmessages()); const auto nextRate = data.vnext_rate(); - const auto rateUpdated = nextRate && (nextRate->v != _searchNextRate); - const auto finished = (type == SearchRequestType::FromStart || type == SearchRequestType::FromOffset) - ? !rateUpdated - : list.empty(); + const auto rateUpdated = nextRate + && (nextRate->v != process->nextRate); + const auto finished = (type.peer || type.migrated || type.posts) + ? list.empty() + : !rateUpdated; if (rateUpdated) { - _searchNextRate = nextRate->v; + process->nextRate = nextRate->v; } if (finished) { - if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { - _searchFullMigrated = true; - } else { - _searchFull = true; - } + process->full = true; } fullCount = data.vcount().v; return list; @@ -2613,33 +2649,26 @@ void Widget::searchReceived( "received messages.channelMessages when no channel " "was passed! (Widget::searchReceived)")); } - if (_searchRequest != 0) { + if (!cacheResults) { // Don't apply cached data! session().data().processUsers(data.vusers()); session().data().processChats(data.vchats()); } - auto list = process(data.vmessages()); + auto list = processList(data.vmessages()); if (list.empty()) { - if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { - _searchFullMigrated = true; - } else { - _searchFull = true; - } + process->full = true; } fullCount = data.vcount().v; return list; }, [&](const MTPDmessages_messagesNotModified &) { - LOG(("API Error: received messages.messagesNotModified! (Widget::searchReceived)")); - if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { - _searchFullMigrated = true; - } else { - _searchFull = true; - } + LOG(("API Error: received messages.messagesNotModified! " + "(Widget::searchReceived)")); + process->full = true; return std::vector>(); }); _inner->searchReceived(messages, inject, type, fullCount); - _searchRequest = 0; + process->requestId = 0; listScrollUpdated(); update(); } @@ -2671,15 +2700,17 @@ void Widget::peerSearchReceived( } } -void Widget::searchApplyEmpty(SearchRequestType type, mtpRequestId id) { - _searchFull = _searchFullMigrated = true; +void Widget::searchApplyEmpty( + SearchRequestType type, + not_null process) { + process->full = true; searchReceived( type, MTP_messages_messages( MTP_vector(), MTP_vector(), MTP_vector()), - id); + process); } void Widget::peerSearchApplyEmpty(mtpRequestId id) { @@ -2696,16 +2727,12 @@ void Widget::peerSearchApplyEmpty(mtpRequestId id) { void Widget::searchFailed( SearchRequestType type, const MTP::Error &error, - mtpRequestId requestId) { + not_null process) { if (error.type() == u"SEARCH_QUERY_EMPTY"_q) { - searchApplyEmpty(type, requestId); - } else if (_searchRequest == requestId) { - _searchRequest = 0; - if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { - _searchFullMigrated = true; - } else { - _searchFull = true; - } + searchApplyEmpty(type, process); + } else { + process->requestId = 0; + process->full = true; } } @@ -2838,6 +2865,7 @@ QString Widget::validateSearchQuery() { } else { _searchHashOrCashtag = IsHashOrCashtagSearchQuery(query); } + _searchWithPostsPreview = computeSearchWithPostsPreview(); return query; } @@ -3096,6 +3124,7 @@ bool Widget::applySearchState(SearchState state) { ? peer->owner().history(migrateFrom).get() : nullptr; _searchState = state; + _searchWithPostsPreview = computeSearchWithPostsPreview(); if (queryChanged) { updateLockUnlockVisibility(anim::type::normal); updateLoadMoreChatsVisibility(); @@ -3111,16 +3140,20 @@ bool Widget::applySearchState(SearchState state) { updateSearchFromVisibility(); updateLockUnlockPosition(); - if ((state.query.isEmpty() && !state.fromPeer && state.tags.empty()) + const auto searchCleared = state.query.isEmpty() + && !state.fromPeer + && state.tags.empty(); + if (searchCleared || inChatChanged || fromPeerChanged || tagsChanged || tabChanged) { - clearSearchCache(); + clearSearchCache(searchCleared); } if (state.query.isEmpty()) { _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(); } _peerSearchQuery = QString(); @@ -3158,15 +3191,23 @@ bool Widget::applySearchState(SearchState state) { return true; } -void Widget::clearSearchCache() { - _searchCache.clear(); +void Widget::clearSearchCache(bool clearPosts) { + _searchProcess.cache.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(); } _searchQuery = QString(); _searchQueryFrom = nullptr; _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(); _topicSearchOffsetDate = 0; _topicSearchOffsetId = _topicSearchOffsetTopicId = 0; @@ -3241,10 +3282,17 @@ void Widget::completeHashtag(QString tag) { if (cur == start + 1 || base::StringViewMid(t, start + 1, cur - start - 1) == base::StringViewMid(tag, 0, cur - start - 1)) { - for (; cur < t.size() && cur - start - 1 < tag.size(); ++cur) { - if (t.at(cur) != tag.at(cur - start - 1)) break; + while (cur < t.size() && cur - start - 1 < tag.size()) { + 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); setSearchQuery(hashtag, start + 1 + tag.size() + 1); applySearchUpdate(); @@ -3356,7 +3404,8 @@ void Widget::updateControlsGeometry() { ? st::dialogsFilterSkip : (st::dialogsFilterPadding.x() + _mainMenu.toggle->width())) + 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 filterAreaHeight = st::topBarHeight; _searchControls->setGeometry(0, filterAreaTop, ratiow, filterAreaHeight); @@ -3369,7 +3418,11 @@ void Widget::updateControlsGeometry() { auto filterTop = (filterAreaHeight - _search->height()) / 2; filterLeft = anim::interpolate(filterLeft, _narrowWidth, narrowRatio); - _search->setGeometryToLeft(filterLeft, filterTop, filterWidth, _search->height()); + _search->setGeometryToLeft( + filterLeft, + filterTop, + filterWidth, + _search->height()); auto mainMenuLeft = anim::interpolate( st::dialogsFilterPadding.x(), @@ -3387,12 +3440,16 @@ void Widget::updateControlsGeometry() { -_searchForNarrowLayout->width(), (_narrowWidth - _searchForNarrowLayout->width()) / 2, narrowRatio); - _searchForNarrowLayout->moveToLeft(searchLeft, st::dialogsFilterPadding.y()); + _searchForNarrowLayout->moveToLeft( + searchLeft, + st::dialogsFilterPadding.y()); auto right = filterLeft + filterWidth; _cancelSearch->moveToLeft(right - _cancelSearch->width(), _search->y()); - right -= _jumpToDate->width(); _jumpToDate->moveToLeft(right, _search->y()); - right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _search->y()); + right -= _jumpToDate->width(); + _jumpToDate->moveToLeft(right, _search->y()); + right -= _chooseFromUser->width(); + _chooseFromUser->moveToLeft(right, _search->y()); const auto barw = width(); const auto expandedStoriesTop = filterAreaTop + filterAreaHeight; @@ -3691,9 +3748,11 @@ void Widget::scrollToEntry(const RowDescriptor &entry) { } 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( - base::take(_searchInHistoryRequest)); + base::take(_historiesRequest)); } PeerData *Widget::searchInPeer() const { @@ -3810,8 +3869,12 @@ bool Widget::cancelSearch(CancelSearchOptions options) { // Don't create suggestions in unfocus case. setInnerFocus(true); } - _lastSearchPeer = nullptr; - _lastSearchId = _lastSearchMigratedId = 0; + _searchProcess.lastPeer = nullptr; + _searchProcess.lastId = 0; + _migratedProcess.lastPeer = nullptr; + _migratedProcess.lastId = 0; + _postsProcess.lastPeer = nullptr; + _postsProcess.lastId = 0; _inner->clearFilter(); applySearchState(std::move(updatedState)); if (_suggestions && clearSearchFocus) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index a1ee3950d..ff751c714 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -75,7 +75,7 @@ class FakeRow; class Key; struct ChosenRow; class InnerWidget; -enum class SearchRequestType : uchar; +struct SearchRequestType; enum class SearchRequestDelay : uchar; class Suggestions; class ChatSearchIn; @@ -151,10 +151,26 @@ protected: void paintEvent(QPaintEvent *e) override; private: + struct SearchProcessState { + base::flat_map cache; + base::flat_map queries; + + PeerData *lastPeer = nullptr; + MsgId lastId = 0; + int32 nextRate = 0; + mtpRequestId requestId = 0; + bool full = false; + }; + void chosenRow(const ChosenRow &row); void listScrollUpdated(); void searchCursorMoved(); void completeHashtag(QString tag); + void requestPublicPosts(bool fromStart); + void requestMessages(bool fromStart); + [[nodiscard]] not_null currentSearchProcess(); + + [[nodiscard]] bool computeSearchWithPostsPreview() const; [[nodiscard]] QString currentSearchQuery() const; [[nodiscard]] int currentSearchQueryCursorPosition() const; @@ -168,7 +184,8 @@ private: void searchReceived( SearchRequestType type, const MTPmessages_Messages &result, - mtpRequestId requestId); + not_null process, + bool cacheResults = false); void peerSearchReceived( const MTPcontacts_Found &result, mtpRequestId requestId); @@ -201,7 +218,7 @@ private: void showCalendar(); void showSearchFrom(); void showMainMenu(); - void clearSearchCache(); + void clearSearchCache(bool clearPosts); void setSearchQuery(const QString &query, int cursorPosition = -1); void updateControlsVisibility(bool fast = false); void updateLockUnlockVisibility( @@ -244,9 +261,11 @@ private: void searchFailed( SearchRequestType type, const MTP::Error &error, - mtpRequestId requestId); + not_null process); void peerSearchFailed(const MTP::Error &error, mtpRequestId requestId); - void searchApplyEmpty(SearchRequestType type, mtpRequestId id); + void searchApplyEmpty( + SearchRequestType type, + not_null process); void peerSearchApplyEmpty(mtpRequestId id); void updateForceDisplayWide(); @@ -318,6 +337,7 @@ private: bool _scrollToTopIsShown = false; bool _forumSearchRequested = false; HashOrCashtag _searchHashOrCashtag = {}; + bool _searchWithPostsPreview = false; Data::Folder *_openedFolder = nullptr; Data::Forum *_openedForum = nullptr; @@ -358,19 +378,13 @@ private: PeerData *_searchQueryFrom = nullptr; std::vector _searchQueryTags; ChatSearchTab _searchQueryTab = {}; - int32 _searchNextRate = 0; - bool _searchFull = false; - bool _searchFullMigrated = false; - int _searchInHistoryRequest = 0; // Not real mtpRequestId. - mtpRequestId _searchRequest = 0; - PeerData *_lastSearchPeer = nullptr; - MsgId _lastSearchId = 0; - MsgId _lastSearchMigratedId = 0; + SearchProcessState _searchProcess; + SearchProcessState _migratedProcess; + SearchProcessState _postsProcess; + int _historiesRequest = 0; // Not real mtpRequestId. - base::flat_map _searchCache; Api::SingleMessageSearch _singleMessageSearch; - base::flat_map _searchQueries; base::flat_map _peerSearchCache; base::flat_map _peerSearchQueries;