Add global search chat type filter.

This commit is contained in:
John Preston 2025-01-03 17:04:08 +04:00
parent 5f10c1875c
commit 44bfdbdc83
8 changed files with 167 additions and 12 deletions

View file

@ -4146,6 +4146,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_search_messages_from" = "Show messages from"; "lng_search_messages_from" = "Show messages from";
"lng_search_messages_n_of_amount" = "{n} of {amount}"; "lng_search_messages_n_of_amount" = "{n} of {amount}";
"lng_search_messages_none" = "No results"; "lng_search_messages_none" = "No results";
"lng_search_filter_all" = "All chats";
"lng_search_filter_private" = "Private chats";
"lng_search_filter_group" = "Group chats";
"lng_search_filter_channel" = "Channels";
"lng_media_save_progress" = "{ready} of {total} {mb}"; "lng_media_save_progress" = "{ready} of {total} {mb}";
"lng_mediaview_save_as" = "Save As..."; "lng_mediaview_save_as" = "Save As...";
@ -5998,6 +6002,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_search_tab_no_results_text" = "There were no results for \"{query}\"."; "lng_search_tab_no_results_text" = "There were no results for \"{query}\".";
"lng_search_tab_no_results_retry" = "Try another hashtag."; "lng_search_tab_no_results_retry" = "Try another hashtag.";
"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it."; "lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it.";
"lng_search_tab_try_in_all" = "Search in All Messages";
"lng_contact_details_button" = "View Contact"; "lng_contact_details_button" = "View Contact";
"lng_contact_details_title" = "Contact details"; "lng_contact_details_title" = "Contact details";

View file

@ -79,6 +79,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_color_indices.h" #include "styles/style_color_indices.h"
#include "styles/style_window.h" #include "styles/style_window.h"
#include "styles/style_media_player.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
@ -143,7 +144,8 @@ constexpr auto kPreviewPostsLimit = 3;
[[nodiscard]] object_ptr<SearchEmpty> MakeSearchEmpty( [[nodiscard]] object_ptr<SearchEmpty> MakeSearchEmpty(
QWidget *parent, QWidget *parent,
SearchState state) { SearchState state,
Fn<void()> resetChatTypeFilter) {
const auto query = state.query.trimmed(); const auto query = state.query.trimmed();
const auto hashtag = !query.isEmpty() && (query[0] == '#'); const auto hashtag = !query.isEmpty() && (query[0] == '#');
const auto trimmed = hashtag ? query.mid(1).trimmed() : query; const auto trimmed = hashtag ? query.mid(1).trimmed() : query;
@ -157,6 +159,9 @@ constexpr auto kPreviewPostsLimit = 3;
const auto waiting = trimmed.isEmpty() const auto waiting = trimmed.isEmpty()
&& state.tags.empty() && state.tags.empty()
&& !fromPeer; && !fromPeer;
const auto suggestAllChats = !waiting
&& state.tab == ChatSearchTab::MyMessages
&& state.filter != ChatTypeFilter::All;
const auto icon = waiting const auto icon = waiting
? SearchEmptyIcon::Search ? SearchEmptyIcon::Search
: SearchEmptyIcon::NoResults; : SearchEmptyIcon::NoResults;
@ -180,7 +185,10 @@ constexpr auto kPreviewPostsLimit = 3;
tr::now, tr::now,
lt_query, lt_query,
trimmed.mid(0, kQueryPreviewLimit))); trimmed.mid(0, kQueryPreviewLimit)));
if (hashtag) { if (suggestAllChats) {
text.append("\n\n").append(
Ui::Text::Link(tr::lng_search_tab_try_in_all(tr::now)));
} else if (hashtag) {
text.append("\n").append( text.append("\n").append(
tr::lng_search_tab_no_results_retry(tr::now)); tr::lng_search_tab_no_results_retry(tr::now));
} }
@ -190,11 +198,29 @@ constexpr auto kPreviewPostsLimit = 3;
parent, parent,
icon, icon,
rpl::single(std::move(text))); rpl::single(std::move(text)));
if (suggestAllChats) {
result->handlerActivated(
) | rpl::start_with_next(resetChatTypeFilter, result->lifetime());
}
result->show(); result->show();
result->resizeToWidth(parent->width()); result->resizeToWidth(parent->width());
return result; return result;
} }
[[nodiscard]] QString ChatTypeFilterLabel(ChatTypeFilter filter) {
switch (filter) {
case ChatTypeFilter::All:
return tr::lng_search_filter_all(tr::now);
case ChatTypeFilter::Private:
return tr::lng_search_filter_private(tr::now);
case ChatTypeFilter::Groups:
return tr::lng_search_filter_group(tr::now);
case ChatTypeFilter::Channels:
return tr::lng_search_filter_channel(tr::now);
}
Unexpected("Chat type filter in search results.");
}
} // namespace } // namespace
struct InnerWidget::CollapsedRow { struct InnerWidget::CollapsedRow {
@ -1184,6 +1210,25 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setFont(st::searchedBarFont); p.setFont(st::searchedBarFont);
p.setPen(st::searchedBarFg); p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
const auto filterOver = _selectedChatTypeFilter
|| _pressedChatTypeFilter;
const auto filterFont = filterOver
? st::searchedBarFont->underline()
: st::searchedBarFont;
if (_searchState.tab == ChatSearchTab::MyMessages) {
const auto text = ChatTypeFilterLabel(_searchState.filter);
if (!_chatTypeFilterWidth) {
_chatTypeFilterWidth = filterFont->width(text);
}
p.setFont(filterFont);
p.drawTextLeft(
(width()
- st::searchedBarPosition.x()
- _chatTypeFilterWidth),
st::searchedBarPosition.y(),
width(),
text);
}
p.translate(0, st::searchedBarHeight); p.translate(0, st::searchedBarHeight);
auto skip = searchedOffset(); auto skip = searchedOffset();
@ -1701,6 +1746,20 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
_searchedSelected = searchedSelected; _searchedSelected = searchedSelected;
updateSelectedRow(); updateSelectedRow();
} }
auto selectedChatTypeFilter = false;
const auto from = skip - st::searchedBarHeight;
if (mouseY <= skip && mouseY >= from) {
const auto left = width()
- _chatTypeFilterWidth
- 2 * st::searchedBarPosition.x();
if (_chatTypeFilterWidth > 0 && local.x() >= left) {
selectedChatTypeFilter = true;
}
}
if (_selectedChatTypeFilter != selectedChatTypeFilter) {
update(0, from, width(), st::searchedBarHeight);
_selectedChatTypeFilter = selectedChatTypeFilter;
}
} }
if (!inTags && wasSelected != isSelected()) { if (!inTags && wasSelected != isSelected()) {
setCursor(wasSelected ? style::cur_default : style::cur_pointer); setCursor(wasSelected ? style::cur_default : style::cur_pointer);
@ -1742,6 +1801,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
setPreviewPressed(_previewSelected); setPreviewPressed(_previewSelected);
setSearchedPressed(_searchedSelected); setSearchedPressed(_searchedSelected);
_pressedMorePosts = _selectedMorePosts; _pressedMorePosts = _selectedMorePosts;
_pressedChatTypeFilter = _selectedChatTypeFilter;
const auto alt = (e->modifiers() & Qt::AltModifier); const auto alt = (e->modifiers() & Qt::AltModifier);
if (alt && showChatPreview()) { if (alt && showChatPreview()) {
@ -2139,6 +2199,8 @@ void InnerWidget::mousePressReleased(
setSearchedPressed(-1); setSearchedPressed(-1);
const auto pressedMorePosts = _pressedMorePosts; const auto pressedMorePosts = _pressedMorePosts;
_pressedMorePosts = false; _pressedMorePosts = false;
const auto pressedChatTypeFilter = _pressedChatTypeFilter;
_pressedChatTypeFilter = false;
if (wasDragging) { if (wasDragging) {
selectByMouse(globalPosition); selectByMouse(globalPosition);
} }
@ -2163,7 +2225,9 @@ void InnerWidget::mousePressReleased(
|| (searchedPressed >= 0 || (searchedPressed >= 0
&& searchedPressed == _searchedSelected) && searchedPressed == _searchedSelected)
|| (pressedMorePosts || (pressedMorePosts
&& pressedMorePosts == _selectedMorePosts)) { && pressedMorePosts == _selectedMorePosts)
|| (pressedChatTypeFilter
&& pressedChatTypeFilter == _selectedChatTypeFilter)) {
if (pressedBotApp && (pressed || filteredPressed >= 0)) { if (pressedBotApp && (pressed || filteredPressed >= 0)) {
const auto &row = pressed const auto &row = pressed
? pressed ? pressed
@ -2690,6 +2754,7 @@ void InnerWidget::clearSelection() {
updateSelectedRow(); updateSelectedRow();
_collapsedSelected = -1; _collapsedSelected = -1;
_selectedMorePosts = false; _selectedMorePosts = false;
_selectedChatTypeFilter = false;
_selected = nullptr; _selected = nullptr;
_filteredSelected _filteredSelected
= _searchedSelected = _searchedSelected
@ -3001,6 +3066,10 @@ void InnerWidget::applySearchState(SearchState state) {
if (state.inChat) { if (state.inChat) {
onHashtagFilterUpdate(QStringView()); onHashtagFilterUpdate(QStringView());
} }
if (state.filter != _searchState.filter) {
_chatTypeFilterWidth = 0;
update();
}
_searchState = std::move(state); _searchState = std::move(state);
_searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query); _searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query);
_searchWithPostsPreview = computeSearchWithPostsPreview(); _searchWithPostsPreview = computeSearchWithPostsPreview();
@ -3269,6 +3338,11 @@ rpl::producer<ChatSearchTab> InnerWidget::changeSearchTabRequests() const {
return _changeSearchTabRequests.events(); return _changeSearchTabRequests.events();
} }
auto InnerWidget::changeSearchFilterRequests() const
-> rpl::producer<ChatTypeFilter>{
return _changeSearchFilterRequests.events();
}
rpl::producer<> InnerWidget::cancelSearchRequests() const { rpl::producer<> InnerWidget::cancelSearchRequests() const {
return _cancelSearchRequests.events(); return _cancelSearchRequests.events();
} }
@ -3557,7 +3631,9 @@ void InnerWidget::refreshEmpty() {
} }
} else if (_searchEmptyState != _searchState) { } else if (_searchEmptyState != _searchState) {
_searchEmptyState = _searchState; _searchEmptyState = _searchState;
_searchEmpty = MakeSearchEmpty(this, _searchState); _searchEmpty = MakeSearchEmpty(this, _searchState, [=] {
_changeSearchFilterRequests.fire(ChatTypeFilter::All);
});
if (_controller->session().data().chatsListLoaded()) { if (_controller->session().data().chatsListLoaded()) {
_searchEmpty->animate(); _searchEmpty->animate();
} }
@ -4288,7 +4364,7 @@ ChosenRow InnerWidget::computeChosenRow() const {
} }
bool InnerWidget::isUserpicPress() const { bool InnerWidget::isUserpicPress() const {
return (_lastRowLocalMouseX >= 0) return (_lastRowLocalMouseX >= 0)
&& (_lastRowLocalMouseX < _st->nameLeft) && (_lastRowLocalMouseX < _st->nameLeft)
&& (_collapsedSelected < 0 && (_collapsedSelected < 0
|| _collapsedSelected >= _collapsedRows.size()); || _collapsedSelected >= _collapsedRows.size());
@ -4308,6 +4384,24 @@ bool InnerWidget::chooseRow(
_changeSearchTabRequests.fire(ChatSearchTab::PublicPosts); _changeSearchTabRequests.fire(ChatSearchTab::PublicPosts);
} }
return true; return true;
} else if (_selectedChatTypeFilter) {
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
st::popupMenuWithIcons);
for (const auto tab : {
ChatTypeFilter::All,
ChatTypeFilter::Private,
ChatTypeFilter::Groups,
ChatTypeFilter::Channels,
}) {
_menu->addAction(ChatTypeFilterLabel(tab), [=] {
_changeSearchFilterRequests.fire_copy(tab);
}, (tab == _searchState.filter)
? &st::mediaPlayerMenuCheck
: nullptr);
}
_menu->popup(QCursor::pos());
return true;
} }
const auto modifyChosenRow = [&]( const auto modifyChosenRow = [&](
ChosenRow row, ChosenRow row,

View file

@ -65,6 +65,7 @@ class SearchEmpty;
class ChatSearchIn; class ChatSearchIn;
enum class HashOrCashtag : uchar; enum class HashOrCashtag : uchar;
struct RightButton; struct RightButton;
enum class ChatTypeFilter : uchar;
struct ChosenRow { struct ChosenRow {
Key key; Key key;
@ -176,6 +177,8 @@ public:
[[nodiscard]] rpl::producer<> listBottomReached() const; [[nodiscard]] rpl::producer<> listBottomReached() const;
[[nodiscard]] auto changeSearchTabRequests() const [[nodiscard]] auto changeSearchTabRequests() const
-> rpl::producer<ChatSearchTab>; -> rpl::producer<ChatSearchTab>;
[[nodiscard]] auto changeSearchFilterRequests() const
-> rpl::producer<ChatTypeFilter>;
[[nodiscard]] rpl::producer<> cancelSearchRequests() const; [[nodiscard]] rpl::producer<> cancelSearchRequests() const;
[[nodiscard]] rpl::producer<> cancelSearchFromRequests() const; [[nodiscard]] rpl::producer<> cancelSearchFromRequests() const;
[[nodiscard]] rpl::producer<> changeSearchFromRequests() const; [[nodiscard]] rpl::producer<> changeSearchFromRequests() const;
@ -308,7 +311,8 @@ private:
|| (_peerSearchPressed >= 0) || (_peerSearchPressed >= 0)
|| (_previewPressed >= 0) || (_previewPressed >= 0)
|| (_searchedPressed >= 0) || (_searchedPressed >= 0)
|| _pressedMorePosts; || _pressedMorePosts
|| _pressedChatTypeFilter;
} }
bool isSelected() const { bool isSelected() const {
return (_collapsedSelected >= 0) return (_collapsedSelected >= 0)
@ -318,7 +322,8 @@ private:
|| (_peerSearchSelected >= 0) || (_peerSearchSelected >= 0)
|| (_previewSelected >= 0) || (_previewSelected >= 0)
|| (_searchedSelected >= 0) || (_searchedSelected >= 0)
|| _selectedMorePosts; || _selectedMorePosts
|| _selectedChatTypeFilter;
} }
bool uniqueSearchResults() const; bool uniqueSearchResults() const;
bool hasHistoryInResults(not_null<History*> history) const; bool hasHistoryInResults(not_null<History*> history) const;
@ -486,6 +491,8 @@ private:
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows; std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
not_null<const style::DialogRow*> _st; not_null<const style::DialogRow*> _st;
mutable std::unique_ptr<Ui::TopicJumpCache> _topicJumpCache; mutable std::unique_ptr<Ui::TopicJumpCache> _topicJumpCache;
bool _selectedChatTypeFilter = false;
bool _pressedChatTypeFilter = false;
bool _selectedMorePosts = false; bool _selectedMorePosts = false;
bool _pressedMorePosts = false; bool _pressedMorePosts = false;
int _collapsedSelected = -1; int _collapsedSelected = -1;
@ -543,6 +550,7 @@ private:
int _previewSelected = -1; int _previewSelected = -1;
int _previewPressed = -1; int _previewPressed = -1;
int _morePostsWidth = 0; int _morePostsWidth = 0;
int _chatTypeFilterWidth = 0;
std::vector<std::unique_ptr<FakeRow>> _searchResults; std::vector<std::unique_ptr<FakeRow>> _searchResults;
int _searchedCount = 0; int _searchedCount = 0;
@ -554,6 +562,7 @@ private:
std::unique_ptr<ChatSearchIn> _searchIn; std::unique_ptr<ChatSearchIn> _searchIn;
rpl::event_stream<ChatSearchTab> _changeSearchTabRequests; rpl::event_stream<ChatSearchTab> _changeSearchTabRequests;
rpl::event_stream<ChatTypeFilter> _changeSearchFilterRequests;
rpl::event_stream<> _cancelSearchRequests; rpl::event_stream<> _cancelSearchRequests;
rpl::event_stream<> _cancelSearchFromRequests; rpl::event_stream<> _cancelSearchFromRequests;
rpl::event_stream<> _changeSearchFromRequests; rpl::event_stream<> _changeSearchFromRequests;

View file

@ -129,11 +129,19 @@ struct EntryState {
const EntryState&) = default; const EntryState&) = default;
}; };
enum class ChatTypeFilter : uchar {
All,
Private,
Groups,
Channels,
};
struct SearchState { struct SearchState {
Key inChat; Key inChat;
PeerData *fromPeer = nullptr; PeerData *fromPeer = nullptr;
std::vector<Data::ReactionId> tags; std::vector<Data::ReactionId> tags;
ChatSearchTab tab = {}; ChatSearchTab tab = {};
ChatTypeFilter filter = ChatTypeFilter::All;
QString query; QString query;
[[nodiscard]] bool empty() const; [[nodiscard]] bool empty() const;

View file

@ -453,6 +453,15 @@ Widget::Widget(
copy.tab = tab; copy.tab = tab;
applySearchState(std::move(copy)); applySearchState(std::move(copy));
}, lifetime()); }, lifetime());
_inner->changeSearchFilterRequests(
) | rpl::filter([=](ChatTypeFilter filter) {
return (_searchState.filter != filter)
&& (_searchState.tab == ChatSearchTab::MyMessages);
}) | rpl::start_with_next([=](ChatTypeFilter filter) {
auto copy = _searchState;
copy.filter = filter;
applySearchState(copy);
}, lifetime());
_inner->cancelSearchRequests( _inner->cancelSearchRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
cancelSearch({ cancelSearch({
@ -2201,6 +2210,7 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
const auto fromPeer = searchFromPeer(); const auto fromPeer = searchFromPeer();
const auto &inTags = searchInTags(); const auto &inTags = searchInTags();
const auto tab = _searchState.tab; const auto tab = _searchState.tab;
const auto filter = _searchState.filter;
const auto fromStartType = SearchRequestType{ const auto fromStartType = SearchRequestType{
.start = true, .start = true,
.peer = (inPeer != nullptr), .peer = (inPeer != nullptr),
@ -2232,6 +2242,7 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
_searchQueryFrom = fromPeer; _searchQueryFrom = fromPeer;
_searchQueryTags = inTags; _searchQueryTags = inTags;
_searchQueryTab = tab; _searchQueryTab = tab;
_searchQueryFilter = filter;
process->nextRate = 0; process->nextRate = 0;
process->full = false; process->full = false;
_migratedProcess.full = false; _migratedProcess.full = false;
@ -2242,12 +2253,14 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
} else if (_searchQuery != query } else if (_searchQuery != query
|| _searchQueryFrom != fromPeer || _searchQueryFrom != fromPeer
|| _searchQueryTags != inTags || _searchQueryTags != inTags
|| _searchQueryTab != tab) { || _searchQueryTab != tab
|| _searchQueryFilter != filter) {
const auto process = currentSearchProcess(); const auto process = currentSearchProcess();
_searchQuery = query; _searchQuery = query;
_searchQueryFrom = fromPeer; _searchQueryFrom = fromPeer;
_searchQueryTags = inTags; _searchQueryTags = inTags;
_searchQueryTab = tab; _searchQueryTab = tab;
_searchQueryFilter = filter;
process->nextRate = 0; process->nextRate = 0;
process->full = false; process->full = false;
_migratedProcess.full = false; _migratedProcess.full = false;
@ -2326,7 +2339,6 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
_peerSearchQuery = peerQuery; _peerSearchQuery = peerQuery;
_peerSearchRequest = 0; _peerSearchRequest = 0;
peerSearchReceived(i->second, 0); peerSearchReceived(i->second, 0);
result = true;
} }
} else if (_peerSearchQuery != peerQuery) { } else if (_peerSearchQuery != peerQuery) {
_peerSearchQuery = peerQuery; _peerSearchQuery = peerQuery;
@ -2598,9 +2610,18 @@ void Widget::requestMessages(bool fromStart) {
const auto type = SearchRequestType{ const auto type = SearchRequestType{
.start = fromStart, .start = fromStart,
}; };
const auto flags = session().settings().skipArchiveInSearch() using Flag = MTPmessages_SearchGlobal::Flag;
? MTPmessages_SearchGlobal::Flag::f_folder_id const auto flags = Flag()
: MTPmessages_SearchGlobal::Flag(0); | (session().settings().skipArchiveInSearch()
? Flag::f_folder_id
: Flag())
| (_searchQueryFilter == ChatTypeFilter::Private
? Flag::f_users_only
: _searchQueryFilter == ChatTypeFilter::Groups
? Flag::f_groups_only
: _searchQueryFilter == ChatTypeFilter::Channels
? Flag::f_broadcasts_only
: Flag());
const auto folderId = 0; const auto folderId = 0;
_searchProcess.requestId = session().api().request( _searchProcess.requestId = session().api().request(
MTPmessages_SearchGlobal( MTPmessages_SearchGlobal(
@ -3184,6 +3205,10 @@ bool Widget::applySearchState(SearchState state) {
const auto queryEmptyChanged = queryChanged const auto queryEmptyChanged = queryChanged
? (_searchState.query.isEmpty() != state.query.isEmpty()) ? (_searchState.query.isEmpty() != state.query.isEmpty())
: false; : false;
if (queryEmptyChanged || tabChanged) {
state.filter = ChatTypeFilter::All;
}
const auto filterChanged = (_searchState.filter != state.filter);
if (forum) { if (forum) {
if (_openedForum == forum) { if (_openedForum == forum) {
@ -3245,6 +3270,7 @@ bool Widget::applySearchState(SearchState state) {
if (searchCleared if (searchCleared
|| inChatChanged || inChatChanged
|| fromPeerChanged || fromPeerChanged
|| filterChanged
|| tagsChanged || tagsChanged
|| tabChanged) { || tabChanged) {
clearSearchCache(searchCleared); clearSearchCache(searchCleared);

View file

@ -381,6 +381,7 @@ private:
PeerData *_searchQueryFrom = nullptr; PeerData *_searchQueryFrom = nullptr;
std::vector<Data::ReactionId> _searchQueryTags; std::vector<Data::ReactionId> _searchQueryTags;
ChatSearchTab _searchQueryTab = {}; ChatSearchTab _searchQueryTab = {};
ChatTypeFilter _searchQueryFilter = {};
SearchProcessState _searchProcess; SearchProcessState _searchProcess;
SearchProcessState _migratedProcess; SearchProcessState _migratedProcess;

View file

@ -62,6 +62,13 @@ void SearchEmpty::setup(Icon icon, rpl::producer<TextWithEntities> text) {
label->move((size.width() - label->width()) / 2, top - sub); label->move((size.width() - label->width()) / 2, top - sub);
}, lifetime()); }, lifetime());
label->setClickHandlerFilter([=](
const ClickHandlerPtr &handler,
Qt::MouseButton) {
_handlerActivated.fire_copy(handler);
return false;
});
_animate = [animate] { _animate = [animate] {
animate(anim::repeat::once); animate(anim::repeat::once);
}; };

View file

@ -29,10 +29,15 @@ public:
void animate(); void animate();
[[nodiscard]] rpl::producer<ClickHandlerPtr> handlerActivated() const {
return _handlerActivated.events();
}
private: private:
void setup(Icon icon, rpl::producer<TextWithEntities> text); void setup(Icon icon, rpl::producer<TextWithEntities> text);
Fn<void()> _animate; Fn<void()> _animate;
rpl::event_stream<ClickHandlerPtr> _handlerActivated;
}; };