From 60aef7871a6220bea90759f85ea8a03b2ee111ea Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 26 Oct 2022 18:18:00 +0400 Subject: [PATCH] Append server-side topic search results. --- Telegram/SourceFiles/data/data_forum.cpp | 54 +++++------ Telegram/SourceFiles/data/data_forum.h | 25 +++-- .../SourceFiles/data/data_forum_topic.cpp | 8 ++ Telegram/SourceFiles/data/data_forum_topic.h | 2 + .../dialogs/dialogs_inner_widget.cpp | 91 +++++++++++++------ .../dialogs/dialogs_inner_widget.h | 8 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 88 +++++++++++++++++- Telegram/SourceFiles/dialogs/dialogs_widget.h | 11 ++- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 10 +- 9 files changed, 224 insertions(+), 73 deletions(-) diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index d55449ece..6d143851d 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -34,7 +34,7 @@ constexpr auto kTopicsFirstLoad = 20; constexpr auto kLoadedTopicsMinCount = 20; constexpr auto kTopicsPerPage = 500; constexpr auto kStalePerRequest = 100; -constexpr auto kGeneralColorId = 0xA9A9A9; +// constexpr auto kGeneralColorId = 0xA9A9A9; } // namespace @@ -113,8 +113,7 @@ void Forum::preloadTopics() { void Forum::reloadTopics() { _topicsList.setLoaded(false); session().api().request(base::take(_requestId)).cancel(); - _offsetDate = 0; - _offsetId = _offsetTopicId = 0; + _offset = {}; for (const auto &[rootId, topic] : _topics) { if (!topic->creating()) { _staleRootIds.emplace(topic->rootId()); @@ -127,18 +126,22 @@ void Forum::requestTopics() { if (_topicsList.loaded() || _requestId) { return; } - const auto firstLoad = !_offsetDate; + const auto firstLoad = !_offset.date; const auto loadCount = firstLoad ? kTopicsFirstLoad : kTopicsPerPage; _requestId = session().api().request(MTPchannels_GetForumTopics( MTP_flags(0), channel()->inputChannel, MTPstring(), // q - MTP_int(_offsetDate), - MTP_int(_offsetId), - MTP_int(_offsetTopicId), + MTP_int(_offset.date), + MTP_int(_offset.id), + MTP_int(_offset.topicId), MTP_int(loadCount) )).done([=](const MTPmessages_ForumTopics &result) { - applyReceivedTopics(result, true); + applyReceivedTopics(result, _offset); + const auto &list = result.data().vtopics().v; + if (list.isEmpty() || list.size() == result.data().vcount().v) { + _topicsList.setLoaded(); + } _requestId = 0; _chatsListChanges.fire({}); if (_topicsList.loaded()) { @@ -155,10 +158,6 @@ void Forum::requestTopics() { }).send(); } -void Forum::applyReceivedTopics(const MTPmessages_ForumTopics &result) { - applyReceivedTopics(result, false); -} - void Forum::applyTopicDeleted(MsgId rootId) { _topicsDeleted.emplace(rootId); @@ -176,7 +175,19 @@ void Forum::applyTopicDeleted(MsgId rootId) { void Forum::applyReceivedTopics( const MTPmessages_ForumTopics &topics, - bool updateOffset) { + ForumOffsets &updateOffsets) { + applyReceivedTopics(topics, [&](not_null topic) { + if (const auto last = topic->lastServerMessage()) { + updateOffsets.date = last->date(); + updateOffsets.id = last->id; + } + updateOffsets.topicId = topic->rootId(); + }); +} + +void Forum::applyReceivedTopics( + const MTPmessages_ForumTopics &topics, + Fn)> callback) { const auto &data = topics.data(); owner().processUsers(data.vusers()); owner().processChats(data.vchats()); @@ -189,9 +200,6 @@ void Forum::applyReceivedTopics( }); _staleRootIds.remove(rootId); topic.match([&](const MTPDforumTopicDeleted &data) { - if (updateOffset) { - LOG(("API Error: Got a deleted topic in getForumTopics.")); - } applyTopicDeleted(rootId); }, [&](const MTPDforumTopic &data) { _topicsDeleted.remove(rootId); @@ -204,26 +212,18 @@ void Forum::applyReceivedTopics( ).first->second.get() : i->second.get(); raw->applyTopic(data); - if (updateOffset) { - if (const auto last = raw->lastServerMessage()) { - _offsetDate = last->date(); - _offsetId = last->id; - } - _offsetTopicId = rootId; + if (callback) { + callback(raw); } }); } - if (updateOffset - && (list.isEmpty() || list.size() == data.vcount().v)) { - _topicsList.setLoaded(); - } if (!_staleRootIds.empty()) { requestSomeStale(); } } void Forum::requestSomeStale() { - if (_staleRequestId || (!_offsetId && _requestId)) { + if (_staleRequestId || (!_offset.id && _requestId)) { return; } const auto type = Histories::RequestType::History; diff --git a/Telegram/SourceFiles/data/data_forum.h b/Telegram/SourceFiles/data/data_forum.h index da41ad9f5..aed485e27 100644 --- a/Telegram/SourceFiles/data/data_forum.h +++ b/Telegram/SourceFiles/data/data_forum.h @@ -24,6 +24,16 @@ namespace Data { class Session; +struct ForumOffsets { + TimeId date = 0; + MsgId id = 0; + MsgId topicId = 0; + + friend inline constexpr auto operator<=>( + ForumOffsets, + ForumOffsets) = default; +}; + class Forum final { public: explicit Forum(not_null history); @@ -57,7 +67,12 @@ public: [[nodiscard]] ForumTopic *enforceTopicFor(MsgId rootId); [[nodiscard]] bool topicDeleted(MsgId rootId) const; - void applyReceivedTopics(const MTPmessages_ForumTopics &topics); + void applyReceivedTopics( + const MTPmessages_ForumTopics &topics, + ForumOffsets &updateOffsets); + void applyReceivedTopics( + const MTPmessages_ForumTopics &topics, + Fn)> callback = nullptr); [[nodiscard]] MsgId reserveCreatingId( const QString &title, @@ -84,10 +99,6 @@ private: void requestSomeStale(); void finishTopicRequest(MsgId rootId); - void applyReceivedTopics( - const MTPmessages_ForumTopics &topics, - bool updateOffset); - const not_null _history; base::flat_map> _topics; @@ -100,9 +111,7 @@ private: mtpRequestId _staleRequestId = 0; mtpRequestId _requestId = 0; - TimeId _offsetDate = 0; - MsgId _offsetId = 0; - MsgId _offsetTopicId = 0; + ForumOffsets _offset; base::flat_set _creatingRootIds; diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 99910b7fb..640bb03d3 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_unread_things.h" #include "history/view/history_view_item_preview.h" #include "main/main_session.h" +#include "base/unixtime.h" #include "ui/painter.h" #include "ui/color_int_conversion.h" #include "styles/style_dialogs.h" @@ -148,6 +149,7 @@ ForumTopic::ForumTopic(not_null forum, MsgId rootId) rootId)) , _rootId(rootId) , _lastKnownServerMessageId(rootId) +, _creationDate(creating() ? base::unixtime::now() : 0) , _flags(creating() ? Flag::My : Flag()) { Thread::setMuted(owner().notifySettings().isMuted(this)); @@ -201,6 +203,10 @@ MsgId ForumTopic::rootId() const { return _rootId; } +TimeId ForumTopic::creationDate() const { + return _creationDate; +} + bool ForumTopic::my() const { return (_flags & Flag::My); } @@ -261,6 +267,8 @@ void ForumTopic::readTillEnd() { void ForumTopic::applyTopic(const MTPDforumTopic &data) { Expects(_rootId == data.vid().v); + _creationDate = data.vdate().v; + applyTitle(qs(data.vtitle())); if (const auto iconId = data.vicon_emoji_id()) { applyIconId(iconId->v); diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index bca37c76b..faf3dbf18 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -60,6 +60,7 @@ public: [[nodiscard]] not_null forum() const; [[nodiscard]] rpl::producer<> destroyed() const; [[nodiscard]] MsgId rootId() const; + [[nodiscard]] TimeId creationDate() const; [[nodiscard]] bool my() const; [[nodiscard]] bool canWrite() const; @@ -171,6 +172,7 @@ private: base::flat_set _titleWords; base::flat_set _titleFirstLetters; int _titleVersion = 0; + TimeId _creationDate = 0; int32 _colorId = 0; Flags _flags; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index af8329fb4..13e816fea 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2061,6 +2061,18 @@ void InnerWidget::onHashtagFilterUpdate(QStringView newFilter) { clearMouseSelection(true); } +void InnerWidget::appendToFiltered(Key key) { + for (const auto &row : _filterResults) { + if (row->key() == key) { + return; + } + } + auto row = std::make_unique(key, _filterResults.size()); + const auto [i, ok] = _filterResultsGlobal.emplace(key, std::move(row)); + _filterResults.emplace_back(i->second.get()); + trackSearchResultsHistory(key.owningHistory()); +} + InnerWidget::~InnerWidget() { clearSearchResults(); } @@ -2069,10 +2081,14 @@ void InnerWidget::clearSearchResults(bool clearPeerSearchResults) { if (clearPeerSearchResults) _peerSearchResults.clear(); _searchResults.clear(); _searchResultsLifetime.destroy(); + _searchResultsHistories.clear(); _searchedCount = _searchedMigratedCount = 0; } void InnerWidget::trackSearchResultsHistory(not_null history) { + if (!_searchResultsHistories.emplace(history).second) { + return; + } const auto channel = history->peer->asChannel(); if (!channel || channel->isBroadcast()) { return; @@ -2088,19 +2104,51 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { row->invalidateTopic(); } } + auto removed = false; + for (auto i = begin(_filterResultsGlobal) + ; i != end(_filterResultsGlobal);) { + if (const auto topic = i->first.topic()) { + if (topic->channel() == channel) { + removed = true; + _filterResults.erase( + ranges::remove(_filterResults, i->first, &Row::key), + end(_filterResults)); + i = _filterResultsGlobal.erase(i); + continue; + } + } + ++i; + } + if (removed) { + refresh(); + clearMouseSelection(true); + } update(); }, _searchResultsLifetime); if (const auto forum = channel->forum()) { forum->topicDestroyed( ) | rpl::start_with_next([=](not_null topic) { - const auto from = ranges::remove( + auto removed = false; + const auto sfrom = ranges::remove( _searchResults, topic.get(), &FakeRow::topic); - if (from != end(_searchResults)) { - _searchResults.erase(from, end(_searchResults)); - refresh(true); + if (sfrom != end(_searchResults)) { + _searchResults.erase(sfrom, end(_searchResults)); + removed = true; + } + const auto ffrom = ranges::remove( + _filterResults, + topic.get(), + &Row::topic); + if (ffrom != end(_filterResults)) { + _filterResults.erase(ffrom, end(_filterResults)); + removed = true; + } + _filterResultsGlobal.erase(Key(topic)); + if (removed) { + refresh(); clearMouseSelection(true); } }, _searchResultsLifetime); @@ -2133,6 +2181,10 @@ void InnerWidget::setLoadMoreCallback(Fn callback) { _loadMoreCallback = std::move(callback); } +void InnerWidget::setLoadMoreFilteredCallback(Fn callback) { + _loadMoreFilteredCallback = std::move(callback); +} + auto InnerWidget::chosenRow() const -> rpl::producer { return _chosenRow.events(); } @@ -2183,8 +2235,14 @@ void InnerWidget::visibleTopBottomUpdated( _visibleTop = visibleTop; _visibleBottom = visibleBottom; loadPeerPhotos(); - if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) - >= height()) { + const auto loadTill = _visibleTop + + PreloadHeightsCount * (_visibleBottom - _visibleTop); + if (_state == WidgetState::Filtered && loadTill >= peerSearchOffset()) { + if (_loadMoreFilteredCallback) { + _loadMoreFilteredCallback(); + } + } + if (loadTill >= height()) { if (_loadMoreCallback) { _loadMoreCallback(); } @@ -2310,31 +2368,12 @@ void InnerWidget::peerSearchReceived( return; } - const auto alreadyAdded = [&](not_null peer) { - for (const auto &row : _filterResults) { - if (const auto history = row->history()) { - if (history->peer == peer) { - return true; - } - } - } - return false; - }; _peerSearchQuery = query.toLower().trimmed(); _peerSearchResults.clear(); _peerSearchResults.reserve(result.size()); for (const auto &mtpPeer : my) { if (const auto peer = session().data().peerLoaded(peerFromMTP(mtpPeer))) { - if (alreadyAdded(peer)) { - continue; - } - auto row = std::make_unique( - peer->owner().history(peer), - _filterResults.size()); - const auto [i, ok] = _filterResultsGlobal.emplace( - peer, - std::move(row)); - _filterResults.emplace_back(i->second.get()); + appendToFiltered(peer->owner().history(peer)); } else { LOG(("API Error: " "user %1 was not loaded in InnerWidget::peopleReceived()" diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 40cba639f..15e00bd4e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -131,10 +131,12 @@ public: void applyFilterUpdate(QString newFilter, bool force = false); void onHashtagFilterUpdate(QStringView newFilter); + void appendToFiltered(Key key); PeerData *updateFromParentDrag(QPoint globalPosition); void setLoadMoreCallback(Fn callback); + void setLoadMoreFilteredCallback(Fn callback); [[nodiscard]] rpl::producer<> listBottomReached() const; [[nodiscard]] rpl::producer<> cancelSearchFromUserRequests() const; [[nodiscard]] rpl::producer chosenRow() const; @@ -389,9 +391,7 @@ private: bool _hashtagDeletePressed = false; std::vector> _filterResults; - base::flat_map< - not_null, - std::unique_ptr> _filterResultsGlobal; + base::flat_map> _filterResultsGlobal; int _filteredSelected = -1; int _filteredPressed = -1; @@ -404,6 +404,7 @@ private: int _peerSearchPressed = -1; std::vector> _searchResults; + base::flat_set> _searchResultsHistories; rpl::lifetime _searchResultsLifetime; int _searchedCount = 0; int _searchedMigratedCount = 0; @@ -432,6 +433,7 @@ private: std::unique_ptr> _videoUserpics; Fn _loadMoreCallback; + Fn _loadMoreFilteredCallback; rpl::event_stream<> _listBottomReached; rpl::event_stream _chosenRow; rpl::event_stream<> _updated; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index ef73d2dfc..80cacac02 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_key.h" #include "dialogs/dialogs_entry.h" #include "history/history.h" +#include "history/history_item.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_contact_status.h" #include "history/view/history_view_requests_bar.h" @@ -350,6 +351,14 @@ Widget::Widget( setAcceptDrops(true); + _inner->setLoadMoreFilteredCallback([=] { + const auto state = _inner->state(); + if (state == WidgetState::Filtered + && !_topicSearchFull + && searchForTopicsRequired(_topicSearchQuery)) { + searchTopics(); + } + }); _inner->setLoadMoreCallback([=] { const auto state = _inner->state(); if (state == WidgetState::Filtered @@ -358,7 +367,7 @@ Widget::Widget( && _searchFull && !_searchFullMigrated))) { searchMore(); - } else if (_openedForum) { + } else if (_openedForum && state == WidgetState::Default) { _openedForum->forum()->requestTopics(); } else { const auto folder = _inner->shownFolder(); @@ -728,6 +737,7 @@ void Widget::changeOpenedSubsection( refreshTopBars(); updateControlsVisibility(true); _peerSearchRequest = 0; + _api.request(base::take(_topicSearchRequest)).cancel(); if (animated == anim::type::normal) { _connecting->setForceHidden(true); _cacheOver = grabForFolderSlideAnimation(); @@ -749,6 +759,7 @@ void Widget::changeOpenedForum(ChannelData *forum, anim::type animated) { cancelSearch(); } _openedForum = forum; + _api.request(base::take(_topicSearchRequest)).cancel(); _inner->changeOpenedForum(forum); }, (forum != nullptr), animated); } @@ -1157,6 +1168,7 @@ bool Widget::searchMessages(bool searchCache) { if (q.isEmpty() && !_searchFromAuthor) { cancelSearchRequest(); _api.request(base::take(_peerSearchRequest)).cancel(); + _api.request(base::take(_topicSearchRequest)).cancel(); return true; } if (searchCache) { @@ -1284,14 +1296,36 @@ bool Widget::searchMessages(bool searchCache) { MTP_vector(0)), 0); } + if (searchForTopicsRequired(query)) { + if (searchCache) { + if (_topicSearchQuery != query) { + result = false; + } + } else if (_topicSearchQuery != query) { + _topicSearchQuery = query; + _topicSearchFull = false; + searchTopics(); + } + } else { + _topicSearchQuery = query; + _topicSearchFull = true; + } return result; } bool Widget::searchForPeersRequired(const QString &query) const { - if (_searchInChat || _openedForum || query.isEmpty()) { - return false; - } - return (query[0] != '#'); + return !_searchInChat + && !_openedForum + && !query.isEmpty() + && (query[0] != '#'); +} + +bool Widget::searchForTopicsRequired(const QString &query) const { + return !_searchInChat + && _openedForum + && !query.isEmpty() + && (query[0] != '#') + && !_openedForum->forum()->topicsList()->loaded(); } void Widget::needSearchMessages() { @@ -1337,6 +1371,45 @@ void Widget::searchMessages( } } +void Widget::searchTopics() { + if (_topicSearchRequest || _topicSearchFull) { + return; + } + _api.request(base::take(_topicSearchRequest)).cancel(); + _topicSearchRequest = _api.request(MTPchannels_GetForumTopics( + MTP_flags(MTPchannels_GetForumTopics::Flag::f_q), + _openedForum->inputChannel, + MTP_string(_topicSearchQuery), + MTP_int(_topicSearchOffsetDate), + MTP_int(_topicSearchOffsetId), + MTP_int(_topicSearchOffsetTopicId), + MTP_int(kSearchPerPage) + )).done([=](const MTPmessages_ForumTopics &result) { + _topicSearchRequest = 0; + const auto savedTopicId = _topicSearchOffsetId; + const auto byCreation = result.data().is_order_by_create_date(); + _openedForum->forum()->applyReceivedTopics(result, [&]( + not_null topic) { + _topicSearchOffsetTopicId = topic->rootId(); + if (byCreation) { + _topicSearchOffsetId = _topicSearchOffsetTopicId; + _topicSearchOffsetDate = topic->creationDate(); + } else if (const auto last = topic->lastServerMessage()) { + _topicSearchOffsetId = last->id; + _topicSearchOffsetDate = last->date(); + } + _inner->appendToFiltered(topic); + }); + if (_topicSearchOffsetTopicId != savedTopicId) { + _inner->refresh(); + } else { + _topicSearchFull = true; + } + }).fail([=] { + _topicSearchFull = true; + }).send(); +} + void Widget::searchMore() { if (_searchRequest || _searchInHistoryRequest) { return; @@ -1833,6 +1906,11 @@ void Widget::clearSearchCache() { } _searchQuery = QString(); _searchQueryFrom = nullptr; + _topicSearchQuery = QString(); + _topicSearchOffsetDate = 0; + _topicSearchOffsetId = _topicSearchOffsetTopicId = 0; + _api.request(base::take(_peerSearchRequest)).cancel(); + _api.request(base::take(_topicSearchRequest)).cancel(); cancelSearchRequest(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index a4523cb61..e07d2ca1f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -88,6 +88,7 @@ public: void scrollToEntry(const RowDescriptor &entry); void searchMessages(const QString &query, Key inChat = {}); + void searchTopics(); void searchMore(); void updateForwardBar(); @@ -150,7 +151,8 @@ private: void setupMainMenuToggle(); void setupDownloadBar(); void setupShortcuts(); - bool searchForPeersRequired(const QString &query) const; + [[nodiscard]] bool searchForPeersRequired(const QString &query) const; + [[nodiscard]] bool searchForTopicsRequired(const QString &query) const; void setSearchInChat(Key chat, PeerData *from = nullptr); void showCalendar(); void showSearchFrom(); @@ -244,6 +246,13 @@ private: bool _peerSearchFull = false; mtpRequestId _peerSearchRequest = 0; + QString _topicSearchQuery; + TimeId _topicSearchOffsetDate = 0; + MsgId _topicSearchOffsetId = 0; + MsgId _topicSearchOffsetTopicId = 0; + bool _topicSearchFull = false; + mtpRequestId _topicSearchRequest = 0; + QString _searchQuery; PeerData *_searchQueryFrom = nullptr; int32 _searchNextRate = 0; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 596612f48..ab331a49a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -624,9 +624,13 @@ void PaintRow( } else { p.setPen(context.active ? st::dialogsNameFgActive - : context.selected - ? st::dialogsArchiveFgOver - : st::dialogsArchiveFg); + : entry->folder() + ? (context.selected + ? st::dialogsArchiveFgOver + : st::dialogsArchiveFg) + : (context.selected + ? st::dialogsNameFgOver + : st::dialogsNameFg)); auto text = entry->chatListName(); // TODO feed name with emoji auto textWidth = st::semiboldFont->width(text); if (textWidth > rectForName.width()) {