diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp index cc97e22c0..4fbcaebf0 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "data/data_channel.h" #include "data/data_histories.h" +#include "data/data_message_reaction_id.h" #include "data/data_peer.h" #include "data/data_session.h" #include "history/history.h" @@ -43,6 +44,23 @@ constexpr auto kSearchPerPage = 50; return result; } +[[nodiscard]] QString RequestToToken( + const MessagesSearch::Request &request) { + auto result = request.query; + if (request.from) { + result += '\n' + QString::number(request.from->id.value); + } + for (const auto &tag : request.tags) { + result += '\n'; + if (const auto customId = tag.custom()) { + result += u"custom"_q + QString::number(customId); + } else { + result += u"emoji"_q + tag.emoji(); + } + } + return result; +} + } // namespace MessagesSearch::MessagesSearch(not_null history) @@ -54,9 +72,8 @@ MessagesSearch::~MessagesSearch() { base::take(_searchInHistoryRequest)); } -void MessagesSearch::searchMessages(const QString &query, PeerData *from) { - _query = query; - _from = from; +void MessagesSearch::searchMessages(Request request) { + _request = std::move(request); _offsetId = {}; searchRequest(); } @@ -69,8 +86,7 @@ void MessagesSearch::searchMore() { } void MessagesSearch::searchRequest() { - const auto nextToken = _query - + QString::number(_from ? _from->id.value : 0); + const auto nextToken = RequestToToken(_request); if (!_offsetId) { const auto it = _cacheOfStartByToken.find(nextToken); if (it != end(_cacheOfStartByToken)) { @@ -80,18 +96,21 @@ void MessagesSearch::searchRequest() { } } auto callback = [=](Fn finish) { - const auto flags = _from - ? MTP_flags(MTPmessages_Search::Flag::f_from_id) - : MTP_flags(0); + using Flag = MTPmessages_Search::Flag; + const auto from = _request.from; + const auto fromPeer = _history->peer->isUser() ? nullptr : from; + const auto savedPeer = _history->peer->isSelf() ? from : nullptr; _requestId = _history->session().api().request(MTPmessages_Search( - flags, + MTP_flags((fromPeer ? Flag::f_from_id : Flag()) + | (savedPeer ? Flag::f_saved_peer_id : Flag()) + | (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)), _history->peer->input, - MTP_string(_query), - (_from - ? _from->input - : MTP_inputPeerEmpty()), - MTPInputPeer(), // saved_peer_id - MTPVector(), // saved_reaction + MTP_string(_request.query), + (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()), + (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()), + MTP_vector_from_range(_request.tags | ranges::views::transform( + Data::ReactionToMTP + )), MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date diff --git a/Telegram/SourceFiles/api/api_messages_search.h b/Telegram/SourceFiles/api/api_messages_search.h index b97ec69aa..76046aaa9 100644 --- a/Telegram/SourceFiles/api/api_messages_search.h +++ b/Telegram/SourceFiles/api/api_messages_search.h @@ -7,10 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/qt/qt_compare.h" +#include "data/data_message_reaction_id.h" + class HistoryItem; class History; class PeerData; +namespace Data { +struct ReactionId; +} // namespace Data + namespace Api { struct FoundMessages { @@ -21,10 +28,23 @@ struct FoundMessages { class MessagesSearch final { public: + struct Request { + QString query; + PeerData *from = nullptr; + std::vector tags; + + friend inline bool operator==( + const Request &, + const Request &) = default; + friend inline auto operator<=>( + const Request &, + const Request &) = default; + }; + explicit MessagesSearch(not_null history); ~MessagesSearch(); - void searchMessages(const QString &query, PeerData *from); + void searchMessages(Request request); void searchMore(); [[nodiscard]] rpl::producer messagesFounds() const; @@ -41,8 +61,7 @@ private: base::flat_map _cacheOfStartByToken; - QString _query; - PeerData *_from = nullptr; + Request _request; MsgId _offsetId; int _searchInHistoryRequest = 0; // Not real mtpRequestId. diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.cpp b/Telegram/SourceFiles/api/api_messages_search_merged.cpp index a1cb69d77..8451232f6 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.cpp +++ b/Telegram/SourceFiles/api/api_messages_search_merged.cpp @@ -11,12 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Api { -bool MessagesSearchMerged::RequestCompare::operator()( - const Request &a, - const Request &b) const { - return (a.query < b.query) && (a.from < b.from); -} - MessagesSearchMerged::MessagesSearchMerged(not_null history) : _apiSearch(history) { if (const auto migrated = history->migrateFrom()) { @@ -88,9 +82,9 @@ void MessagesSearchMerged::clear() { void MessagesSearchMerged::search(const Request &search) { if (_migratedSearch) { _waitingForTotal = true; - _migratedSearch->searchMessages(search.query, search.from); + _migratedSearch->searchMessages(search); } - _apiSearch.searchMessages(search.query, search.from); + _apiSearch.searchMessages(search); } void MessagesSearchMerged::searchMore() { diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.h b/Telegram/SourceFiles/api/api_messages_search_merged.h index d7c67713f..d4c6fbc1c 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.h +++ b/Telegram/SourceFiles/api/api_messages_search_merged.h @@ -12,19 +12,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; class PeerData; +namespace Data { +struct ReactionId; +} // namespace Data + namespace Api { // Search in both of history and migrated history, if it exists. class MessagesSearchMerged final { public: - struct Request { - QString query; - PeerData *from = nullptr; - }; - struct RequestCompare { - bool operator()(const Request &a, const Request &b) const; - }; - using CachedRequests = std::set; + using Request = MessagesSearch::Request; + using CachedRequests = base::flat_set; MessagesSearchMerged(not_null history); diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp index 99fe6b883..0a939ace6 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp @@ -221,7 +221,7 @@ void DeleteMessagesBox::prepare() { ? QString() : QString(" (%1)").arg(total)); }); - search->searchMessages(QString(), _moderateFrom); + search->searchMessages({ .from = _moderateFrom }); } } else { details.text = (_ids.size() == 1) diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.cpp b/Telegram/SourceFiles/data/data_message_reaction_id.cpp index 2c1a9e503..1e93abfcd 100644 --- a/Telegram/SourceFiles/data/data_message_reaction_id.cpp +++ b/Telegram/SourceFiles/data/data_message_reaction_id.cpp @@ -31,6 +31,15 @@ ReactionId SearchTagFromQuery(const QString &query) { return {}; } +std::vector SearchTagsFromQuery( + const QString &query) { + auto result = std::vector(); + if (const auto tag = SearchTagFromQuery(query)) { + result.push_back(tag); + } + return result; +} + QString ReactionEntityData(const ReactionId &id) { if (id.empty()) { return {}; diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.h b/Telegram/SourceFiles/data/data_message_reaction_id.h index 53d4a2df8..69f765871 100644 --- a/Telegram/SourceFiles/data/data_message_reaction_id.h +++ b/Telegram/SourceFiles/data/data_message_reaction_id.h @@ -47,6 +47,8 @@ struct MessageReaction { [[nodiscard]] QString SearchTagToQuery(const ReactionId &tagId); [[nodiscard]] ReactionId SearchTagFromQuery(const QString &query); +[[nodiscard]] std::vector SearchTagsFromQuery( + const QString &query); [[nodiscard]] QString ReactionEntityData(const ReactionId &id); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index f3b5d1752..2a16376cc 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1928,11 +1928,10 @@ void Widget::searchMessages(QString query, Key inChat) { controller()->closeFolder(); } - auto tags = std::vector(); - if (const auto tagId = Data::SearchTagFromQuery(query)) { + auto tags = Data::SearchTagsFromQuery(query); + if (!tags.empty()) { inChat = session().data().history(session().user()); query = QString(); - tags.push_back(tagId); } const auto inChatChanged = [&] { const auto inPeer = inChat.peer(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e14148752..d59e7c36a 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4818,10 +4818,12 @@ void HistoryWidget::searchInChatEmbedded(std::optional query) { updateControlsGeometry(); }; + const auto from = (PeerData*)nullptr; _composeSearch = std::make_unique( this, controller(), _history, + from, query.value_or(QString())); update(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp index d1dbbd0e3..36c22e4d6 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp @@ -9,12 +9,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_messages_search_merged.h" #include "boxes/peer_list_box.h" +#include "core/click_handler_types.h" +#include "core/ui_integration.h" +#include "data/data_message_reactions.h" +#include "data/data_saved_messages.h" #include "data/data_session.h" +#include "data/data_user.h" #include "dialogs/dialogs_search_from_controllers.h" // SearchFromBox +#include "dialogs/dialogs_search_tags.h" #include "dialogs/ui/dialogs_layout.h" #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" +#include "main/main_session.h" #include "ui/effects/show_animation.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" @@ -255,7 +262,12 @@ List CreateList( class TopBar final : public Ui::RpWidget { public: - TopBar(not_null parent, const QString &query); + TopBar( + not_null parent, + not_null window, + not_null history, + PeerData *from, + const QString &query); void setInnerFocus(); void setQuery(const QString &query); @@ -275,11 +287,17 @@ protected: private: void clearItems(); + void setupTags( + not_null window, + not_null history, + PeerData *from); void requestSearch(bool cache = true); void requestSearchDelayed(); base::unique_qptr _cancel; + std::vector _searchTagsSelected; base::unique_qptr _select; + std::unique_ptr _searchTags; rpl::variable _from = nullptr; @@ -293,29 +311,39 @@ private: rpl::event_stream> _keyEvents; }; -TopBar::TopBar(not_null parent, const QString &query) +TopBar::TopBar( + not_null parent, + not_null window, + not_null history, + PeerData *from, + const QString &query) : Ui::RpWidget(parent) , _cancel(base::make_unique_q(this, st::historyTopBarBack)) +, _searchTagsSelected(Data::SearchTagsFromQuery(query)) , _select(base::make_unique_q( this, st::searchInChatMultiSelect, tr::lng_dlg_filter(), - query)) + _searchTagsSelected.empty() ? query : QString())) , _searchTimer([=] { requestSearch(); }) { + setupTags(window, history, from); - parent->geometryValue( - ) | rpl::start_with_next([=](const QRect &r) { + rpl::combine( + parent->geometryValue(), + _searchTags ? _searchTags->heightValue() : rpl::single(0) + ) | rpl::start_with_next([=](const QRect &r, int tagsHeight) { moveToLeft(0, 0); - resize(r.width(), st::topBarHeight); + resize(r.width(), st::topBarHeight + tagsHeight); }, lifetime()); sizeValue( ) | rpl::start_with_next([=](const QSize &s) { - _cancel->moveToLeft(0, (s.height() - _cancel->height()) / 2); + const auto height = st::topBarHeight; + _cancel->moveToLeft(0, (height - _cancel->height()) / 2); const auto selectLeft = _cancel->x() + _cancel->width(); _select->resizeToWidth(s.width() - selectLeft); - _select->moveToLeft(selectLeft, (s.height() - _select->height()) / 2); + _select->moveToLeft(selectLeft, (height - _select->height()) / 2); }, lifetime()); @@ -337,6 +365,10 @@ TopBar::TopBar(not_null parent, const QString &query) _select->setCancelledCallback([=] { _cancelRequests.fire({}); }); + + if (from) { + setFrom(from); + } } void TopBar::keyPressEvent(QKeyEvent *e) { @@ -372,8 +404,94 @@ void TopBar::clearItems() { }); } +void TopBar::setupTags( + not_null window, + not_null history, + PeerData *from) { + if (!_searchTagsSelected.empty()) { + history = history->owner().history(history->session().user()); + } else if (!history->peer->isSelf()) { + _searchTags = nullptr; + return; + } + const auto reactions = &history->owner().reactions(); + const auto sublist = from + ? history->owner().savedMessages().sublist(from).get() + : nullptr; + _searchTags = std::make_unique( + &history->owner(), + reactions->myTagsValue(sublist), + _searchTagsSelected); + + _searchTags->selectedValue( + ) | rpl::start_with_next([=](std::vector &&list) { + _searchTagsSelected = std::move(list); + requestSearch(false); + }, _searchTags->lifetime()); + + const auto parent = Ui::CreateChild(this); + const auto padding = st::searchInChatTagsPadding; + const auto position = QPoint(padding.left(), padding.top()); + + _searchTags->repaintRequests() | rpl::start_with_next([=] { + parent->update(); + }, _searchTags->lifetime()); + + widthValue() | rpl::start_with_next([=](int width) { + width -= padding.left() + padding.right(); + _searchTags->resizeToWidth(width); + }, _searchTags->lifetime()); + + rpl::combine( + widthValue(), + _searchTags->heightValue() + ) | rpl::start_with_next([=](int width, int height) { + height += padding.top() + padding.bottom(); + parent->setGeometry(0, st::topBarHeight, width, height); + }, _searchTags->lifetime()); + + parent->paintRequest() | rpl::start_with_next([=](const QRect &r) { + auto p = Painter(parent); + p.fillRect(r, st::dialogsBg); + _searchTags->paint(p, position, crl::now(), false); + }, parent->lifetime()); + + parent->setMouseTracking(true); + parent->events() | rpl::start_with_next([=](not_null e) { + if (e->type() == QEvent::MouseMove) { + const auto mouse = static_cast(e.get()); + const auto point = mouse->pos() - position; + const auto handler = _searchTags->lookupHandler(point); + ClickHandler::setActive(handler); + parent->setCursor(handler + ? style::cur_pointer + : style::cur_default); + } else if (e->type() == QEvent::MouseButtonPress) { + const auto mouse = static_cast(e.get()); + if (mouse->button() == Qt::LeftButton) { + ClickHandler::pressed(); + } + } else if (e->type() == QEvent::MouseButtonRelease) { + const auto mouse = static_cast(e.get()); + if (mouse->button() == Qt::LeftButton) { + const auto handler = ClickHandler::unpressed(); + ActivateClickHandler(parent, handler, ClickContext{ + .button = mouse->button(), + .other = QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = window, + }), + }); + } + } + }, parent->lifetime()); +} + void TopBar::requestSearch(bool cache) { - const auto search = SearchRequest{ _select->getQuery(), _from.current() }; + const auto search = SearchRequest{ + _select->getQuery(), + _from.current(), + _searchTagsSelected + }; if (cache) { _typedRequests.insert(search); } @@ -382,7 +500,11 @@ void TopBar::requestSearch(bool cache) { void TopBar::requestSearchDelayed() { // Check cached queries. - const auto search = SearchRequest{ _select->getQuery(), _from.current() }; + const auto search = SearchRequest{ + _select->getQuery(), + _from.current(), + _searchTagsSelected + }; if (_typedRequests.contains(search)) { requestSearch(false); return; @@ -655,6 +777,7 @@ public: not_null parent, not_null window, not_null history, + PeerData *from, const QString &query); ~Inner(); @@ -693,10 +816,11 @@ ComposeSearch::Inner::Inner( not_null parent, not_null window, not_null history, + PeerData *from, const QString &query) : _window(window) , _history(history) -, _topBar(base::make_unique_q(parent, query)) +, _topBar(base::make_unique_q(parent, window, history, from, query)) , _bottomBar(base::make_unique_q(parent, HasChooseFrom(history))) , _list(CreateList(parent, history)) , _apiSearch(history) { @@ -720,7 +844,7 @@ ComposeSearch::Inner::Inner( _topBar->searchRequests( ) | rpl::start_with_next([=](const SearchRequest &search) { - if (search.query.isEmpty() && !search.from) { + if (search.query.isEmpty() && !search.from && search.tags.empty()) { return; } _apiSearch.clear(); @@ -893,8 +1017,9 @@ ComposeSearch::ComposeSearch( not_null parent, not_null window, not_null history, + PeerData *from, const QString &query) -: _inner(std::make_unique(parent, window, history, query)) { +: _inner(std::make_unique(parent, window, history, from, query)) { } ComposeSearch::~ComposeSearch() { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.h index 6ce99dbbb..9090c0daa 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.h @@ -25,6 +25,7 @@ public: not_null parent, not_null window, not_null history, + PeerData *from = nullptr, const QString &query = QString()); ~ComposeSearch(); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 1237f3e34..6ec3aeb04 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -912,6 +912,7 @@ searchInChatPeerListItem: PeerListItem(defaultPeerListItem) { searchInChatPeerList: PeerList(defaultPeerList) { item: searchInChatPeerListItem; } +searchInChatTagsPadding: margins(6px, 0px, 6px, 0px); msgServiceGiftBoxSize: size(236px, 231px); // Plus msgServiceGiftBoxTopSkip. msgServiceGiftBoxRadius: 20px;