diff --git a/Telegram/Resources/icons/menu/chats.png b/Telegram/Resources/icons/menu/chats.png new file mode 100644 index 000000000..e97616ee0 Binary files /dev/null and b/Telegram/Resources/icons/menu/chats.png differ diff --git a/Telegram/Resources/icons/menu/chats@2x.png b/Telegram/Resources/icons/menu/chats@2x.png new file mode 100644 index 000000000..6682dbcf6 Binary files /dev/null and b/Telegram/Resources/icons/menu/chats@2x.png differ diff --git a/Telegram/Resources/icons/menu/chats@3x.png b/Telegram/Resources/icons/menu/chats@3x.png new file mode 100644 index 000000000..7ac3ab7d0 Binary files /dev/null and b/Telegram/Resources/icons/menu/chats@3x.png differ diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 4a910953e..dee018efd 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "ui/basic.style"; +using "ui/layers/layers.style"; // boxRoundShadow using "ui/widgets/widgets.style"; DialogRow { @@ -446,10 +447,25 @@ dialogsLoadMoreLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) size: size(12px, 12px); } -dialogsSearchInHeight: 52px; -dialogsSearchInPhotoSize: 36px; -dialogsSearchInPhotoPadding: 10px; -dialogsSearchInSkip: 7px; +dialogsSearchInHeight: 38px; +dialogsSearchInPhotoSize: 26px; +dialogsSearchInPhotoPadding: 12px; +dialogsSearchInSkip: 10px; +dialogsSearchInNameTop: 9px; +dialogsSearchInDownTop: 15px; +dialogsSearchInDown: icon {{ "intro_country_dropdown", windowBoldFg }}; +dialogsSearchInDownSkip: 4px; +dialogsSearchInMenu: PopupMenu(defaultPopupMenu) { + shadow: boxRoundShadow; + animation: PanelAnimation(defaultPanelAnimation) { + shadow: boxRoundShadow; + } + scrollPadding: margins(0px, 0px, 0px, 0px); + radius: 8px; + menu: menuWithIcons; +} +dialogsSearchInCheck: icon {{ "player/player_check", mediaPlayerActiveFg }}; +dialogsSearchInCheckSkip: 8px; dialogsSearchFromStyle: defaultTextStyle; dialogsSearchFromPalette: TextPalette(defaultTextPalette) { linkFg: dialogsNameFg; @@ -694,17 +710,17 @@ dialogsStoriesTooltipHide: IconButton(defaultIconButton) { ripple: emptyRippleAnimation; } -searchedBarHeight: 32px; +searchedBarHeight: 28px; searchedBarFont: normalFont; -searchedBarPosition: point(17px, 7px); +searchedBarPosition: point(14px, 5px); searchedBarLabel: FlatLabel(defaultFlatLabel) { textFg: searchedBarFg; - margin: margins(17px, 7px, 17px, 7px); + margin: margins(14px, 5px, 14px, 5px); } searchedBarLink: LinkButton(defaultLinkButton) { color: searchedBarFg; overColor: searchedBarFg; - padding: margins(17px, 7px, 17px, 7px); + padding: margins(14px, 5px, 14px, 5px); } dialogsSearchTagSkip: point(8px, 4px); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 3216e512c..2614a3341 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_three_state_icon.h" #include "dialogs/ui/chat_search_empty.h" -#include "dialogs/ui/chat_search_tabs.h" +#include "dialogs/ui/chat_search_in.h" #include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_video_userpic.h" @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/text/text_utilities.h" #include "ui/text/text_options.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "ui/ui_utility.h" #include "data/data_drafts.h" @@ -137,9 +138,7 @@ constexpr auto kStartReorderThreshold = 30; if (hashtag) { text.append(tr::lng_search_tab_by_hashtag(tr::now)); } else { - text.append( - tr::lng_dlg_search_for_messages(tr::now) - ).append('\n').append(Ui::Text::Link(tr::lng_cancel(tr::now))); + text.append(tr::lng_dlg_search_for_messages(tr::now)); } } else { text.append(tr::lng_search_tab_no_results( @@ -214,12 +213,9 @@ InnerWidget::InnerWidget( , _narrowWidth(st::defaultDialogRow.padding.left() + st::defaultDialogRow.photoSize + st::defaultDialogRow.padding.left()) -, _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) , _childListShown(std::move(childListShown)) { setAttribute(Qt::WA_OpaquePaintEvent, true); - _cancelSearchFromUser->hide(); - style::PaletteChanged( ) | rpl::start_with_next([=] { _topicJumpCache = nullptr; @@ -553,11 +549,7 @@ int InnerWidget::searchTagsOffset() const { } int InnerWidget::searchInChatOffset() const { - auto result = searchTagsOffset(); - if (_searchTags) { - result += _searchTags->height(); - } - return result; + return searchTagsOffset() + (_searchTags ? _searchTags->height() : 0); } int InnerWidget::searchedOffset() const { @@ -567,11 +559,7 @@ int InnerWidget::searchedOffset() const { } int InnerWidget::searchInChatSkip() const { - auto result = 0; - if (_searchFromShown) { - result += st::dialogsSearchInHeight; - } - return result; + return _searchIn ? _searchIn->height() : 0; } void InnerWidget::changeOpenedFolder(Data::Folder *folder) { @@ -928,14 +916,14 @@ void InnerWidget::paintEvent(QPaintEvent *e) { p.translate(0, _searchTags->height()); top += _searchTags->height(); } - if (_searchFromShown) { - paintSearchInChat(p, { - .st = &st::forumTopicRow, - .currentBg = currentBg(), - .now = ms, - .width = fullWidth, - .paused = videoPaused, - }); + if (_searchIn) { + //paintSearchInChat(p, { + // .st = &st::forumTopicRow, + // .currentBg = currentBg(), + // .now = ms, + // .width = fullWidth, + // .paused = videoPaused, + //}); p.translate(0, searchInChatSkip()); top += searchInChatSkip(); if (_searchResults.empty()) { @@ -1211,111 +1199,112 @@ void InnerWidget::paintSearchTags( const auto position = QPoint(_searchTagsLeft, top); _searchTags->paint(p, position, context.now, context.paused); } - -void InnerWidget::paintSearchInChat( - Painter &p, - const Ui::PaintContext &context) const { - auto height = searchInChatSkip(); - - auto top = 0; - p.setFont(st::searchedBarFont); - auto fullRect = QRect(0, top, width(), height - top); - p.fillRect(fullRect, currentBg()); - if (_searchFromShown) { - p.setPen(st::dialogsTextFg); - p.setTextPalette(st::dialogsSearchFromPalette); - paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText); - p.restoreTextPalette(); - } -} -template -void InnerWidget::paintSearchInFilter( - Painter &p, - PaintUserpic paintUserpic, - int top, - const style::icon *icon, - const Ui::Text::String &text) const { - const auto savedPen = p.pen(); - const auto userpicLeft = st::defaultDialogRow.padding.left(); - const auto userpicTop = top - + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2; - paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize); - - const auto nameleft = st::defaultDialogRow.padding.left() - + st::dialogsSearchInPhotoSize - + st::dialogsSearchInPhotoPadding; - const auto namewidth = width() - - nameleft - - st::defaultDialogRow.padding.left() - - st::defaultDialogRow.padding.right() - - st::dialogsCancelSearch.width; - auto rectForName = QRect( - nameleft, - top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2, - namewidth, - st::semiboldFont->height); - if (icon) { - icon->paint(p, rectForName.topLeft(), width()); - rectForName.setLeft(rectForName.left() - + icon->width() - + st::dialogsChatTypeSkip); - } - p.setPen(savedPen); - text.drawLeftElided( - p, - rectForName.left(), - rectForName.top(), - rectForName.width(), - width()); -} - -void InnerWidget::paintSearchInPeer( - Painter &p, - not_null peer, - Ui::PeerUserpicView &userpic, - int top, - const Ui::Text::String &text) const { - const auto paintUserpic = [&](Painter &p, int x, int y, int size) { - peer->paintUserpicLeft(p, userpic, x, y, width(), size); - }; - const auto icon = Ui::ChatTypeIcon(peer); - paintSearchInFilter(p, paintUserpic, top, icon, text); -} - -void InnerWidget::paintSearchInSaved( - Painter &p, - int top, - const Ui::Text::String &text) const { - const auto paintUserpic = [&](Painter &p, int x, int y, int size) { - Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size); - }; - paintSearchInFilter(p, paintUserpic, top, nullptr, text); -} - -void InnerWidget::paintSearchInReplies( - Painter &p, - int top, - const Ui::Text::String &text) const { - const auto paintUserpic = [&](Painter &p, int x, int y, int size) { - Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size); - }; - paintSearchInFilter(p, paintUserpic, top, nullptr, text); -} - -void InnerWidget::paintSearchInTopic( - Painter &p, - const Ui::PaintContext &context, - not_null topic, - Ui::PeerUserpicView &userpic, - int top, - const Ui::Text::String &text) const { - const auto paintUserpic = [&](Painter &p, int x, int y, int size) { - p.translate(x, y); - topic->paintUserpic(p, userpic, context); - p.translate(-x, -y); - }; - paintSearchInFilter(p, paintUserpic, top, nullptr, text); -} +// +//void InnerWidget::paintSearchInChat( +// Painter &p, +// const Ui::PaintContext &context) const { +// auto height = searchInChatSkip(); +// +// auto top = 0; +// p.setFont(st::searchedBarFont); +// auto fullRect = QRect(0, top, width(), height - top); +// p.fillRect(fullRect, currentBg()); +// if (_searchFromShown) { +// p.setPen(st::dialogsTextFg); +// p.setTextPalette(st::dialogsSearchFromPalette); +// paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText); +// p.restoreTextPalette(); +// } +//} +// +//template +//void InnerWidget::paintSearchInFilter( +// Painter &p, +// PaintUserpic paintUserpic, +// int top, +// const style::icon *icon, +// const Ui::Text::String &text) const { +// const auto savedPen = p.pen(); +// const auto userpicLeft = st::defaultDialogRow.padding.left(); +// const auto userpicTop = top +// + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2; +// paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize); +// +// const auto nameleft = st::defaultDialogRow.padding.left() +// + st::dialogsSearchInPhotoSize +// + st::dialogsSearchInPhotoPadding; +// const auto namewidth = width() +// - nameleft +// - st::defaultDialogRow.padding.left() +// - st::defaultDialogRow.padding.right() +// - st::dialogsCancelSearch.width; +// auto rectForName = QRect( +// nameleft, +// top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2, +// namewidth, +// st::semiboldFont->height); +// if (icon) { +// icon->paint(p, rectForName.topLeft(), width()); +// rectForName.setLeft(rectForName.left() +// + icon->width() +// + st::dialogsChatTypeSkip); +// } +// p.setPen(savedPen); +// text.drawLeftElided( +// p, +// rectForName.left(), +// rectForName.top(), +// rectForName.width(), +// width()); +//} +// +//void InnerWidget::paintSearchInPeer( +// Painter &p, +// not_null peer, +// Ui::PeerUserpicView &userpic, +// int top, +// const Ui::Text::String &text) const { +// const auto paintUserpic = [&](Painter &p, int x, int y, int size) { +// peer->paintUserpicLeft(p, userpic, x, y, width(), size); +// }; +// const auto icon = Ui::ChatTypeIcon(peer); +// paintSearchInFilter(p, paintUserpic, top, icon, text); +//} +// +//void InnerWidget::paintSearchInSaved( +// Painter &p, +// int top, +// const Ui::Text::String &text) const { +// const auto paintUserpic = [&](Painter &p, int x, int y, int size) { +// Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size); +// }; +// paintSearchInFilter(p, paintUserpic, top, nullptr, text); +//} +// +//void InnerWidget::paintSearchInReplies( +// Painter &p, +// int top, +// const Ui::Text::String &text) const { +// const auto paintUserpic = [&](Painter &p, int x, int y, int size) { +// Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size); +// }; +// paintSearchInFilter(p, paintUserpic, top, nullptr, text); +//} +// +//void InnerWidget::paintSearchInTopic( +// Painter &p, +// const Ui::PaintContext &context, +// not_null topic, +// Ui::PeerUserpicView &userpic, +// int top, +// const Ui::Text::String &text) const { +// const auto paintUserpic = [&](Painter &p, int x, int y, int size) { +// p.translate(x, y); +// topic->paintUserpic(p, userpic, context); +// p.translate(-x, -y); +// }; +// paintSearchInFilter(p, paintUserpic, top, nullptr, text); +//} void InnerWidget::mouseMoveEvent(QMouseEvent *e) { if (_chatPreviewTouchGlobal || _touchDragStartGlobal) { @@ -1989,17 +1978,20 @@ void InnerWidget::resizeEvent(QResizeEvent *e) { _searchTags->resizeToWidth(width() - 2 * _searchTagsLeft); } resizeEmpty(); - moveCancelSearchButtons(); + moveSearchIn(); } -void InnerWidget::moveCancelSearchButtons() { - const auto widthForCancelButton = qMax( +void InnerWidget::moveSearchIn() { + if (!_searchIn) { + return; + } + const auto searchInWidth = std::max( width(), st::columnMinimalWidthLeft - _narrowWidth); - const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchFromUser->width(); - const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2; - const auto skip = (_searchTags ? _searchTags->height() : 0); - _cancelSearchFromUser->moveToLeft(left, skip + top); + _searchIn->resizeToWidth(searchInWidth); + + const auto top = (_searchTags ? _searchTags->height() : 0); + _searchIn->moveToLeft(0, top); } void InnerWidget::dialogRowReplaced( @@ -2656,7 +2648,7 @@ void InnerWidget::applySearchState(SearchState state) { 1 ) | rpl::start_with_next([=] { refresh(); - moveCancelSearchButtons(); + moveSearchIn(); }, _searchTags->lifetime()); } else { _searchTags = nullptr; @@ -2670,17 +2662,12 @@ void InnerWidget::applySearchState(SearchState state) { if (state.inChat) { onHashtagFilterUpdate(QStringView()); } - if (_searchFromShown) { - _cancelSearchFromUser->show(); - _searchFromUserUserpic = _searchFromShown->createUserpicView(); - } else { - _cancelSearchFromUser->hide(); - _searchFromUserUserpic = {}; - } - refreshSearchInChatLabel(); - moveCancelSearchButtons(); - _searchState = std::move(state); + _searchingHashtag = IsHashtagSearchQuery(_searchState.query); + + updateSearchIn(); + moveSearchIn(); + auto newFilter = _searchState.query; const auto mentionsSearch = (newFilter == u"@"_q); const auto words = mentionsSearch @@ -2924,12 +2911,20 @@ rpl::producer<> InnerWidget::listBottomReached() const { return _listBottomReached.events(); } -rpl::producer<> InnerWidget::cancelSearchFromUserRequests() const { - return _cancelSearchFromUser->clicks() | rpl::to_empty; +rpl::producer InnerWidget::changeSearchTabRequests() const { + return _changeSearchTabRequests.events(); } rpl::producer<> InnerWidget::cancelSearchRequests() const { - return _cancelSearch.events(); + return _cancelSearchRequests.events(); +} + +rpl::producer<> InnerWidget::cancelSearchFromRequests() const { + return _cancelSearchFromRequests.events(); +} + +rpl::producer<> InnerWidget::changeSearchFromRequests() const { + return _changeSearchFromRequests.events(); } rpl::producer InnerWidget::mustScrollTo() const { @@ -3200,9 +3195,6 @@ void InnerWidget::refreshEmpty() { } else if (_searchEmptyState != _searchState) { _searchEmptyState = _searchState; _searchEmpty = MakeSearchEmpty(this, _searchState); - _searchEmpty->linkClicks() | rpl::start_with_next([=] { - _cancelSearch.fire({}); - }, _searchEmpty->lifetime()); if (_controller->session().data().chatsListLoaded()) { _searchEmpty->animate(); } @@ -3342,19 +3334,76 @@ auto InnerWidget::searchTagsChanges() const : rpl::never>(); } -void InnerWidget::refreshSearchInChatLabel() { - const auto from = _searchFromShown ? _searchFromShown->name() : u""_q; - if (!from.isEmpty()) { - const auto fromUserText = tr::lng_dlg_search_from( - tr::now, - lt_user, - Ui::Text::Semibold(from), - Ui::Text::WithEntities); - _searchFromUserText.setMarkedText( - st::dialogsSearchFromStyle, - fromUserText, - Ui::DialogTextOptions()); +void InnerWidget::updateSearchIn() { + if (!_searchState.inChat && !_searchingHashtag) { + _searchIn = nullptr; + return; + } else if (!_searchIn) { + _searchIn = std::make_unique(this); + _searchIn->show(); + _searchIn->changeFromRequests() | rpl::start_to_stream( + _changeSearchFromRequests, + _searchIn->lifetime()); + _searchIn->cancelFromRequests() | rpl::start_to_stream( + _cancelSearchFromRequests, + _searchIn->lifetime()); + _searchIn->cancelInRequests() | rpl::start_to_stream( + _cancelSearchRequests, + _searchIn->lifetime()); + _searchIn->tabChanges() | rpl::start_to_stream( + _changeSearchTabRequests, + _searchIn->lifetime()); } + + const auto sublist = _searchState.inChat.sublist(); + const auto topic = _searchState.inChat.topic(); + const auto peer = _searchState.inChat.owningHistory() + ? _searchState.inChat.owningHistory()->peer.get() + : _openedForum + ? _openedForum->channel().get() + : nullptr; + const auto topicIcon = !topic + ? nullptr + : topic->iconId() + ? Ui::MakeEmojiThumbnail( + &topic->owner(), + Data::SerializeCustomEmojiId(topic->iconId())) + : Ui::MakeEmojiThumbnail( + &topic->owner(), + Data::TopicIconEmojiEntity({ + .title = (topic->isGeneral() + ? Data::ForumGeneralIconTitle() + : topic->title()), + .colorId = (topic->isGeneral() + ? Data::ForumGeneralIconColor(st::windowSubTextFg->c) + : topic->colorId()), + })); + const auto peerIcon = peer + ? Ui::MakeUserpicThumbnail(peer) + : sublist + ? Ui::MakeUserpicThumbnail(sublist->peer()) + : nullptr; + const auto myIcon = Ui::MakeIconThumbnail(st::menuIconChats); + const auto publicIcon = _searchingHashtag + ? Ui::MakeIconThumbnail(st::menuIconChannel) + : nullptr; + const auto peerTabType = (peer && peer->isBroadcast()) + ? ChatSearchPeerTabType::Channel + : (peer && (peer->isChat() || peer->isMegagroup())) + ? ChatSearchPeerTabType::Group + : ChatSearchPeerTabType::Chat; + const auto fromImage = _searchFromShown + ? Ui::MakeUserpicThumbnail(_searchFromShown) + : nullptr; + const auto fromName = _searchFromShown + ? _searchFromShown->shortName() + : QString(); + _searchIn->apply({ + { ChatSearchTab::ThisTopic, topicIcon }, + { ChatSearchTab::ThisPeer, peerIcon }, + { ChatSearchTab::MyMessages, myIcon }, + { ChatSearchTab::PublicPosts, publicIcon }, + }, _searchState.tab, peerTabType, fromImage, fromName); } void InnerWidget::repaintSearchResult(int index) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 9f069c243..9d74bf8cb 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -61,6 +61,7 @@ class FakeRow; class IndexedList; class SearchTags; class SearchEmpty; +class ChatSearchIn; struct ChosenRow { Key key; @@ -156,8 +157,11 @@ public: void setLoadMoreCallback(Fn callback); void setLoadMoreFilteredCallback(Fn callback); [[nodiscard]] rpl::producer<> listBottomReached() const; - [[nodiscard]] rpl::producer<> cancelSearchFromUserRequests() const; + [[nodiscard]] auto changeSearchTabRequests() const + -> rpl::producer; [[nodiscard]] rpl::producer<> cancelSearchRequests() const; + [[nodiscard]] rpl::producer<> cancelSearchFromRequests() const; + [[nodiscard]] rpl::producer<> changeSearchFromRequests() const; [[nodiscard]] rpl::producer chosenRow() const; [[nodiscard]] rpl::producer<> updated() const; @@ -358,38 +362,38 @@ private: void paintSearchTags( Painter &p, const Ui::PaintContext &context) const; - void paintSearchInChat( - Painter &p, - const Ui::PaintContext &context) const; - void paintSearchInPeer( - Painter &p, - not_null peer, - Ui::PeerUserpicView &userpic, - int top, - const Ui::Text::String &text) const; - void paintSearchInSaved( - Painter &p, - int top, - const Ui::Text::String &text) const; - void paintSearchInReplies( - Painter &p, - int top, - const Ui::Text::String &text) const; - void paintSearchInTopic( - Painter &p, - const Ui::PaintContext &context, - not_null topic, - Ui::PeerUserpicView &userpic, - int top, - const Ui::Text::String &text) const; - template - void paintSearchInFilter( - Painter &p, - PaintUserpic paintUserpic, - int top, - const style::icon *icon, - const Ui::Text::String &text) const; - void refreshSearchInChatLabel(); + //void paintSearchInChat( + // Painter &p, + // const Ui::PaintContext &context) const; + //void paintSearchInPeer( + // Painter &p, + // not_null peer, + // Ui::PeerUserpicView &userpic, + // int top, + // const Ui::Text::String &text) const; + //void paintSearchInSaved( + // Painter &p, + // int top, + // const Ui::Text::String &text) const; + //void paintSearchInReplies( + // Painter &p, + // int top, + // const Ui::Text::String &text) const; + //void paintSearchInTopic( + // Painter &p, + // const Ui::PaintContext &context, + // not_null topic, + // Ui::PeerUserpicView &userpic, + // int top, + // const Ui::Text::String &text) const; + //template + //void paintSearchInFilter( + // Painter &p, + // PaintUserpic paintUserpic, + // int top, + // const style::icon *icon, + // const Ui::Text::String &text) const; + void updateSearchIn(); void repaintSearchResult(int index); Ui::VideoUserpic *validateVideoUserpic(not_null row); @@ -415,7 +419,7 @@ private: void savePinnedOrder(); bool pinnedShiftAnimationCallback(crl::time now); void handleChatListEntryRefreshes(); - void moveCancelSearchButtons(); + void moveSearchIn(); void dragPinnedFromTouch(); void saveChatsFilterScrollState(FilterId filterId); @@ -490,19 +494,22 @@ private: WidgetState _state = WidgetState::Default; + std::unique_ptr _searchIn; + rpl::event_stream _changeSearchTabRequests; + rpl::event_stream<> _cancelSearchRequests; + rpl::event_stream<> _cancelSearchFromRequests; + rpl::event_stream<> _changeSearchFromRequests; object_ptr _loadingAnimation = { nullptr }; object_ptr _searchEmpty = { nullptr }; SearchState _searchEmptyState; object_ptr _empty = { nullptr }; - object_ptr _cancelSearchFromUser; - rpl::event_stream<> _cancelSearch; Ui::DraggingScrollManager _draggingScroll; SearchState _searchState; + bool _searchingHashtag = false; History *_searchInMigrated = nullptr; PeerData *_searchFromShown = nullptr; - mutable Ui::PeerUserpicView _searchFromUserUserpic; Ui::Text::String _searchFromUserText; std::unique_ptr _searchTags; int _searchTagsLeft = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp index 7c1be62aa..544c48cca 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_folder.h" #include "data/data_forum_topic.h" #include "data/data_saved_sublist.h" -#include "dialogs/ui/chat_search_tabs.h" +#include "dialogs/ui/chat_search_in.h" #include "history/history.h" namespace Dialogs { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 7ff420704..d421b0508 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qt/qt_key_modifiers.h" #include "base/options.h" -#include "dialogs/ui/chat_search_tabs.h" +#include "dialogs/ui/chat_search_in.h" #include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_list.h" #include "dialogs/ui/dialogs_suggestions.h" @@ -336,7 +336,20 @@ Widget::Widget( ) | rpl::start_with_next([=] { searchCursorMoved(); }, lifetime()); - _inner->cancelSearchFromUserRequests( + _inner->changeSearchTabRequests( + ) | rpl::filter([=](ChatSearchTab tab) { + return _searchState.tab != tab; + }) | rpl::start_with_next([=](ChatSearchTab tab) { + auto copy = _searchState; + copy.tab = tab; + applySearchState(std::move(copy)); + }, lifetime()); + _inner->cancelSearchRequests( + ) | rpl::start_with_next([=] { + setInnerFocus(true); + applySearchState({}); + }, lifetime()); + _inner->cancelSearchFromRequests( ) | rpl::start_with_next([=] { auto copy = _searchState; copy.fromPeer = nullptr; @@ -345,10 +358,9 @@ Widget::Widget( } applySearchState(std::move(copy)); }, lifetime()); - _inner->cancelSearchRequests( + _inner->changeSearchFromRequests( ) | rpl::start_with_next([=] { - setInnerFocus(true); - applySearchState({}); + showSearchFrom(); }, lifetime()); _inner->chosenRow( ) | rpl::start_with_next([=](const ChosenRow &row) { @@ -1096,9 +1108,6 @@ void Widget::updateControlsVisibility(bool fast) { updateJumpToDateVisibility(fast); updateSearchFromVisibility(fast); } - if (_searchTabs) { - _searchTabs->show(); - } if (_connecting) { _connecting->setForceHidden(false); } @@ -1242,102 +1251,6 @@ void Widget::updateSuggestions(anim::type animated) { } } -void Widget::updateSearchTabs() { - const auto has = _searchState.inChat || _searchingHashtag; - if (!has) { - if (_searchTabs) { - _searchTabs = nullptr; - updateControlsGeometry(); - } - return; - } else if (!_searchTabs) { - const auto savedSession = &session(); - const auto markedTextContext = [=](Fn repaint) { - return Core::MarkedTextContext{ - .session = savedSession, - .customEmojiRepaint = std::move(repaint), - }; - }; - _searchTabs = std::make_unique( - this, - _searchState.tab, - std::move(markedTextContext)); - _searchTabs->setVisible(!_showAnimation); - _searchTabs->tabChanges( - ) | rpl::filter([=](ChatSearchTab tab) { - return (_searchState.tab != tab); - }) | rpl::start_with_next([=](ChatSearchTab tab) { - auto copy = _searchState; - copy.tab = tab; - applySearchState(std::move(copy)); - }, _searchTabs->lifetime()); - } - const auto sublist = _searchState.inChat.sublist(); - const auto topic = _searchState.inChat.topic(); - const auto peer = _searchState.inChat.owningHistory() - ? _searchState.inChat.owningHistory()->peer.get() - : _openedForum - ? _openedForum->channel().get() - : nullptr; - const auto topicShortLabel = !topic - ? TextWithEntities() - : topic->iconId() - ? Ui::Text::SingleCustomEmoji( - Data::SerializeCustomEmojiId(topic->iconId())) - : Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({ - .title = (topic->isGeneral() - ? Data::ForumGeneralIconTitle() - : topic->title()), - .colorId = (topic->isGeneral() - ? Data::ForumGeneralIconColor(st::windowSubTextFg->c) - : topic->colorId()), - })); - const auto peerShortLabel = peer - ? Ui::Text::SingleCustomEmoji( - session().data().customEmojiManager().peerUserpicEmojiData( - peer, - {}, - true)) - : sublist - ? Ui::Text::SingleCustomEmoji( - session().data().customEmojiManager().peerUserpicEmojiData( - sublist->peer(), - {}, - true)) - : TextWithEntities(); - const auto myShortLabel = DefaultShortLabel(ChatSearchTab::MyMessages); - const auto publicShortLabel = _searchingHashtag - ? DefaultShortLabel(ChatSearchTab::PublicPosts) - : TextWithEntities(); - if ((_searchState.tab == ChatSearchTab::ThisTopic - && !_searchState.inChat.topic()) - || (_searchState.tab == ChatSearchTab::ThisPeer - && !_searchState.inChat - && !_openedForum) - || (_searchState.tab == ChatSearchTab::PublicPosts - && !_searchingHashtag)) { - _searchState.tab = _searchState.inChat.topic() - ? ChatSearchTab::ThisTopic - : (_searchState.inChat.owningHistory() - || _searchState.inChat.sublist()) - ? ChatSearchTab::ThisPeer - : ChatSearchTab::MyMessages; - } - const auto peerTabType = (peer && peer->isBroadcast()) - ? ChatSearchPeerTabType::Channel - : (peer && (peer->isChat() || peer->isMegagroup())) - ? ChatSearchPeerTabType::Group - : ChatSearchPeerTabType::Chat; - _searchTabs->setTabShortLabels({ - { ChatSearchTab::ThisTopic, topicShortLabel }, - { ChatSearchTab::ThisPeer, peerShortLabel }, - { ChatSearchTab::MyMessages, myShortLabel }, - { ChatSearchTab::PublicPosts, publicShortLabel }, - }, _searchState.tab, peerTabType); - - updateControlsGeometry(); -} - void Widget::changeOpenedSubsection( FnMut change, bool fromRight, @@ -2732,9 +2645,8 @@ QString Widget::validateSearchQuery() { setSearchQuery(fixed.text, fixed.cursorPosition); } return fixed.text; - } else if (_searchingHashtag != IsHashtagSearchQuery(query)) { - _searchingHashtag = !_searchingHashtag; - updateSearchTabs(); + } else { + _searchingHashtag = IsHashtagSearchQuery(query); } return query; } @@ -2932,7 +2844,7 @@ bool Widget::applySearchState(SearchState state) { state.tab = (_openedForum && !state.inChat) ? ChatSearchTab::ThisPeer : ChatSearchTab::MyMessages; - } else if (!state.inChat && !_searchTabs) { + } else if (!state.inChat && !_searchingHashtag) { state.tab = (forum || _openedForum) ? ChatSearchTab::ThisPeer : ChatSearchTab::MyMessages; @@ -2966,6 +2878,20 @@ bool Widget::applySearchState(SearchState state) { return false; } + if ((state.tab == ChatSearchTab::ThisTopic + && !state.inChat.topic()) + || (state.tab == ChatSearchTab::ThisPeer + && !state.inChat + && !_openedForum) + || (state.tab == ChatSearchTab::PublicPosts + && !_searchingHashtag)) { + state.tab = state.inChat.topic() + ? ChatSearchTab::ThisTopic + : (state.inChat.owningHistory() || state.inChat.sublist()) + ? ChatSearchTab::ThisPeer + : ChatSearchTab::MyMessages; + } + const auto migrateFrom = (peer && !topic) ? peer->migrateFrom() : nullptr; @@ -2979,7 +2905,6 @@ bool Widget::applySearchState(SearchState state) { } if (inChatChanged) { controller()->setSearchInChat(_searchState.inChat); - updateSearchTabs(); } if (queryChanged || inChatChanged) { updateCancelSearch(); @@ -3334,9 +3259,6 @@ void Widget::updateControlsGeometry() { if (_forumRequestsBar) { _forumRequestsBar->resizeToWidth(barw); } - if (_searchTabs) { - _searchTabs->resizeToWidth(barw); - } _updateScrollGeometryCached = [=] { const auto moreChatsBarTop = expandedStoriesTop + ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded); @@ -3358,13 +3280,8 @@ void Widget::updateControlsGeometry() { if (_forumReportBar) { _forumReportBar->bar().move(0, forumReportTop); } - const auto searchTabsTop = forumReportTop + const auto scrollTop = forumReportTop + (_forumReportBar ? _forumReportBar->bar().height() : 0); - if (_searchTabs) { - _searchTabs->move(0, searchTabsTop); - } - const auto scrollTop = searchTabsTop - + (_searchTabs ? _searchTabs->height() : 0); const auto scrollHeight = height() - scrollTop - bottomSkip; const auto wasScrollHeight = _scroll->height(); _scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 33d239a48..29e68a3cc 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -76,7 +76,7 @@ struct ChosenRow; class InnerWidget; enum class SearchRequestType; class Suggestions; -class ChatSearchTabs; +class ChatSearchIn; enum class ChatSearchTab : uchar; class Widget final : public Window::AbstractSectionWidget { @@ -255,7 +255,6 @@ private: void updateScrollUpPosition(); void updateLockUnlockPosition(); void updateSuggestions(anim::type animated); - void updateSearchTabs(); void processSearchFocusChange(); [[nodiscard]] bool redirectToSearchPossible() const; @@ -294,7 +293,6 @@ private: QPointer _inner; std::unique_ptr _suggestions; std::vector> _hidingSuggestions; - std::unique_ptr _searchTabs; class BottomButton; object_ptr _updateTelegram = { nullptr }; object_ptr _loadMoreChats = { nullptr }; diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp index 855112f85..7c3c7f393 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp @@ -33,12 +33,6 @@ void SearchEmpty::setup(Icon icon, rpl::producer text) { this, std::move(text), st::defaultPeerListAbout); - label->setClickHandlerFilter([=](const auto &, Qt::MouseButton button) { - if (button == Qt::LeftButton) { - _linkClicks.fire({}); - } - return false; - }); const auto size = st::recentPeersEmptySize; const auto animation = [&] { switch (icon) { diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h index 6ddf52676..0ad901ad9 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h @@ -26,9 +26,6 @@ public: rpl::producer text); void setMinimalHeight(int minimalHeight); - [[nodiscard]] rpl::producer<> linkClicks() const { - return _linkClicks.events(); - } void animate(); @@ -36,7 +33,6 @@ private: void setup(Icon icon, rpl::producer text); Fn _animate; - rpl::event_stream<> _linkClicks; }; diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp new file mode 100644 index 000000000..0524b3a63 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp @@ -0,0 +1,459 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "dialogs/ui/chat_search_in.h" + +#include "lang/lang_keys.h" +#include "ui/effects/ripple_animation.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/menu/menu_item_base.h" +#include "ui/dynamic_image.h" +#include "ui/painter.h" +#include "styles/style_dialogs.h" +#include "styles/style_window.h" + +namespace Dialogs { +namespace { + +class Action final : public Ui::Menu::ItemBase { +public: + Action( + not_null parentMenu, + std::shared_ptr icon, + const QString &label, + bool chosen); + + bool isEnabled() const override; + not_null action() const override; + + void handleKeyPress(not_null e) override; + +protected: + QPoint prepareRippleStartPosition() const override; + QImage prepareRippleMask() const override; + + int contentHeight() const override; + +private: + void paint(Painter &p); + + void resolveMinWidth(); + void refreshDimensions(); + + const not_null _parentMenu; + const not_null _dummyAction; + const style::Menu &_st; + const int _height = 0; + + std::shared_ptr _icon; + Ui::Text::String _text; + bool _checked = false; + +}; + +[[nodiscard]] QString TabLabel( + ChatSearchTab tab, + ChatSearchPeerTabType type = {}) { + switch (tab) { + case ChatSearchTab::MyMessages: + return tr::lng_search_tab_my_messages(tr::now); + case ChatSearchTab::ThisTopic: + return tr::lng_search_tab_this_topic(tr::now); + case ChatSearchTab::ThisPeer: + switch (type) { + case ChatSearchPeerTabType::Chat: + return tr::lng_search_tab_this_chat(tr::now); + case ChatSearchPeerTabType::Channel: + return tr::lng_search_tab_this_channel(tr::now); + case ChatSearchPeerTabType::Group: + return tr::lng_search_tab_this_group(tr::now); + } + Unexpected("Type in Dialogs::TabLabel."); + case ChatSearchTab::PublicPosts: + return tr::lng_search_tab_public_posts(tr::now); + } + Unexpected("Tab in Dialogs::TabLabel."); +} + +Action::Action( + not_null parentMenu, + std::shared_ptr icon, + const QString &label, + bool chosen) +: ItemBase(parentMenu->menu(), parentMenu->menu()->st()) +, _parentMenu(parentMenu) +, _dummyAction(CreateChild(parentMenu->menu().get())) +, _st(parentMenu->menu()->st()) +, _height(st::dialogsSearchInHeight) +, _icon(std::move(icon)) +, _checked(chosen) { + const auto parent = parentMenu->menu(); + + _text.setText(st::semiboldTextStyle, label); + _icon->subscribeToUpdates([=] { update(); }); + + initResizeHook(parent->sizeValue()); + resolveMinWidth(); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + paint(p); + }, lifetime()); + + enableMouseSelecting(); +} + +void Action::resolveMinWidth() { + const auto maxWidth = st::dialogsSearchInPhotoPadding + + st::dialogsSearchInPhotoSize + + st::dialogsSearchInSkip + + _text.maxWidth() + + st::dialogsSearchInCheckSkip + + st::dialogsSearchInCheck.width() + + st::dialogsSearchInCheckSkip; + setMinWidth(maxWidth); +} + +void Action::paint(Painter &p) { + const auto enabled = isEnabled(); + const auto selected = isSelected(); + if (selected && _st.itemBgOver->c.alpha() < 255) { + p.fillRect(0, 0, width(), _height, _st.itemBg); + } + const auto &bg = selected ? _st.itemBgOver : _st.itemBg; + p.fillRect(0, 0, width(), _height, bg); + if (enabled) { + paintRipple(p, 0, 0); + } + + auto x = st::dialogsSearchInPhotoPadding; + const auto photos = st::dialogsSearchInPhotoSize; + const auto photoy = (height() - photos) / 2; + p.drawImage(QRect{ x, photoy, photos, photos }, _icon->image(photos)); + x += photos + st::dialogsSearchInSkip; + const auto available = width() + - x + - st::dialogsSearchInCheckSkip + - st::dialogsSearchInCheck.width() + - st::dialogsSearchInCheckSkip; + + p.setPen(!enabled + ? _st.itemFgDisabled + : selected + ? _st.itemFgOver + : _st.itemFg); + _text.drawLeftElided( + p, + x, + st::dialogsSearchInNameTop, + available, + width()); + x += available; + if (_checked) { + x += st::dialogsSearchInCheckSkip; + const auto &icon = st::dialogsSearchInCheck; + const auto icony = (height() - icon.height()) / 2; + icon.paint(p, x, icony, width()); + } +} + +bool Action::isEnabled() const { + return true; +} + +not_null Action::action() const { + return _dummyAction; +} + +QPoint Action::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()); +} + +QImage Action::prepareRippleMask() const { + return Ui::RippleAnimation::RectMask(size()); +} + +int Action::contentHeight() const { + return _height; +} + +void Action::handleKeyPress(not_null e) { + if (!isSelected()) { + return; + } + const auto key = e->key(); + if (key == Qt::Key_Enter || key == Qt::Key_Return) { + setClicked(Ui::Menu::TriggeredSource::Keyboard); + } +} + +} // namespace + +FixedHashtagSearchQuery FixHashtagSearchQuery( + const QString &query, + int cursorPosition) { + const auto trimmed = query.trimmed(); + const auto hash = int(trimmed.isEmpty() + ? query.size() + : query.indexOf(trimmed)); + const auto start = std::min(cursorPosition, hash); + auto result = query.mid(0, start); + for (const auto &ch : query.mid(start)) { + if (ch.isSpace()) { + if (cursorPosition > result.size()) { + --cursorPosition; + } + continue; + } else if (result.size() == start) { + result += '#'; + if (ch != '#') { + ++cursorPosition; + } + } + if (ch != '#') { + result += ch; + } + } + if (result.size() == start) { + result += '#'; + ++cursorPosition; + } + return { result, cursorPosition }; +} + +bool IsHashtagSearchQuery(const QString &query) { + const auto trimmed = query.trimmed(); + if (trimmed.isEmpty() || trimmed[0] != '#') { + return false; + } + for (const auto &ch : trimmed) { + if (ch.isSpace()) { + return false; + } + } + return true; +} + +void ChatSearchIn::Section::update() { + outer->update(); +} + +ChatSearchIn::ChatSearchIn(QWidget *parent) +: RpWidget(parent) { + _in.clicks.events() | rpl::start_with_next([=] { + showMenu(); + }, lifetime()); +} + +ChatSearchIn::~ChatSearchIn() = default; + +void ChatSearchIn::apply( + std::vector tabs, + ChatSearchTab active, + ChatSearchPeerTabType peerTabType, + std::shared_ptr fromUserpic, + QString fromName) { + _tabs = std::move(tabs); + _peerTabType = peerTabType; + _active = active; + const auto i = ranges::find(_tabs, active, &PossibleTab::tab); + Assert(i != end(_tabs)); + Assert(i->icon != nullptr); + updateSection( + &_in, + i->icon->clone(), + Ui::Text::Semibold(TabLabel(active, peerTabType))); + + auto text = tr::lng_dlg_search_from( + tr::now, + lt_user, + Ui::Text::Semibold(fromName), + Ui::Text::WithEntities); + updateSection(&_from, std::move(fromUserpic), std::move(text)); + + resizeToWidth(width()); +} + +rpl::producer<> ChatSearchIn::cancelInRequests() const { + return _in.cancelRequests.events(); +} + +rpl::producer<> ChatSearchIn::cancelFromRequests() const { + return _from.cancelRequests.events(); +} + +rpl::producer<> ChatSearchIn::changeFromRequests() const { + return _from.clicks.events(); +} + +rpl::producer ChatSearchIn::tabChanges() const { + return _active.changes(); +} + +void ChatSearchIn::showMenu() { + _menu = base::make_unique_q( + this, + st::dialogsSearchInMenu); + const auto active = _active.current(); + auto activeIndex = 0; + for (const auto &tab : _tabs) { + if (!tab.icon) { + continue; + } + const auto value = tab.tab; + if (value == active) { + activeIndex = _menu->actions().size(); + } + auto action = base::make_unique_q( + _menu.get(), + tab.icon, + TabLabel(value, _peerTabType), + (value == active)); + action->setClickedCallback([=] { + _active = value; + }); + _menu->addAction(std::move(action)); + } + const auto count = int(_menu->actions().size()); + const auto bottomLeft = (activeIndex * 2 >= count); + const auto single = st::dialogsSearchInHeight; + const auto in = mapToGlobal(_in.outer->pos() + + QPoint(0, bottomLeft ? count * single : 0)); + _menu->setForcedOrigin(bottomLeft + ? Ui::PanelAnimation::Origin::BottomLeft + : Ui::PanelAnimation::Origin::TopLeft); + if (_menu->prepareGeometryFor(in)) { + _menu->move(_menu->pos() - QPoint(_menu->inner().x(), activeIndex * single)); + _menu->popupPrepared(); + } +} + +void ChatSearchIn::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + const auto top = QRect(0, 0, width(), st::searchedBarHeight); + p.fillRect(top, st::searchedBarBg); + p.fillRect(rect().translated(0, st::searchedBarHeight), st::dialogsBg); + + p.setFont(st::searchedBarFont); + p.setPen(st::searchedBarFg); + p.drawTextLeft( + st::searchedBarPosition.x(), + st::searchedBarPosition.y(), + width(), + tr::lng_dlg_search_in(tr::now)); +} + +int ChatSearchIn::resizeGetHeight(int newWidth) { + auto result = st::searchedBarHeight; + if (const auto raw = _in.outer.get()) { + raw->resizeToWidth(newWidth); + raw->move(0, result); + result += raw->height(); + _in.shadow->setGeometry(0, result, newWidth, st::lineWidth); + result += st::lineWidth; + } + if (const auto raw = _from.outer.get()) { + raw->resizeToWidth(newWidth); + raw->move(0, result); + result += raw->height(); + _from.shadow->setGeometry(0, result, newWidth, st::lineWidth); + result += st::lineWidth; + } + return result; +} + +void ChatSearchIn::updateSection( + not_null section, + std::shared_ptr image, + TextWithEntities text) { + if (section->subscribed) { + section->image->subscribeToUpdates(nullptr); + section->subscribed = false; + } + if (!image) { + if (section->outer) { + section->cancel = nullptr; + section->shadow = nullptr; + section->outer = nullptr; + section->subscribed = false; + } + return; + } else if (!section->outer) { + auto button = std::make_unique(this); + const auto raw = button.get(); + section->outer = std::move(button); + + raw->resize( + st::columnMinimalWidthLeft, + st::dialogsSearchInHeight); + + raw->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(raw); + if (!section->subscribed) { + section->subscribed = true; + section->image->subscribeToUpdates([=] { + raw->update(); + }); + } + const auto outer = raw->width(); + const auto size = st::dialogsSearchInPhotoSize; + const auto left = st::dialogsSearchInPhotoPadding; + const auto top = (st::dialogsSearchInHeight - size) / 2; + p.drawImage( + QRect{ left, top, size, size }, + section->image->image(size)); + + const auto x = left + size + st::dialogsSearchInSkip; + const auto available = outer + - st::dialogsSearchInSkip + - section->cancel->width() + - 2 * st::dialogsSearchInDownSkip + - st::dialogsSearchInDown.width() + - x; + const auto use = std::min(section->text.maxWidth(), available); + const auto iconx = x + use + st::dialogsSearchInDownSkip; + const auto icony = st::dialogsSearchInDownTop; + st::dialogsSearchInDown.paint(p, iconx, icony, outer); + p.setPen(st::windowBoldFg); + section->text.draw(p, { + .position = QPoint(x, st::dialogsSearchInNameTop), + .outerWidth = outer, + .availableWidth = available, + .elisionLines = 1, + }); + }, raw->lifetime()); + + section->shadow = std::make_unique(this); + section->shadow->show(); + + const auto st = &st::dialogsCancelSearchInPeer; + section->cancel = std::make_unique(raw, *st); + section->cancel->show(); + raw->sizeValue() | rpl::start_with_next([=](QSize size) { + const auto left = size.width() - section->cancel->width(); + const auto top = (size.height() - st->height) / 2; + section->cancel->moveToLeft(left, top); + }, section->cancel->lifetime()); + section->cancel->clicks() | rpl::to_empty | rpl::start_to_stream( + section->cancelRequests, + section->cancel->lifetime()); + + raw->clicks() | rpl::to_empty | rpl::start_to_stream( + section->clicks, + raw->lifetime()); + + raw->show(); + } + section->image = std::move(image); + section->text.setMarkedText(st::dialogsSearchFromStyle, std::move(text)); +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.h b/Telegram/SourceFiles/dialogs/ui/chat_search_in.h new file mode 100644 index 000000000..5e99f2d05 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.h @@ -0,0 +1,100 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/unique_qptr.h" +#include "ui/rp_widget.h" + +namespace Ui { +class PlainShadow; +class DynamicImage; +class IconButton; +class PopupMenu; +} // namespace Ui + +namespace Dialogs { + +enum class ChatSearchTab : uchar { + MyMessages, + ThisTopic, + ThisPeer, + PublicPosts, +}; + +enum class ChatSearchPeerTabType : uchar { + Chat, + Channel, + Group, +}; + +class ChatSearchIn final : public Ui::RpWidget { +public: + explicit ChatSearchIn(QWidget *parent); + ~ChatSearchIn(); + + struct PossibleTab { + ChatSearchTab tab = {}; + std::shared_ptr icon; + }; + void apply( + std::vector tabs, + ChatSearchTab active, + ChatSearchPeerTabType peerTabType, + std::shared_ptr fromUserpic, + QString fromName); + + [[nodiscard]] rpl::producer<> cancelInRequests() const; + [[nodiscard]] rpl::producer<> cancelFromRequests() const; + [[nodiscard]] rpl::producer<> changeFromRequests() const; + [[nodiscard]] rpl::producer tabChanges() const; + +private: + struct Section { + std::unique_ptr outer; + std::unique_ptr cancel; + std::unique_ptr shadow; + std::shared_ptr image; + Ui::Text::String text; + rpl::event_stream<> clicks; + rpl::event_stream<> cancelRequests; + bool subscribed = false; + + void update(); + }; + + int resizeGetHeight(int newWidth) override; + void paintEvent(QPaintEvent *e) override; + void showMenu(); + + void updateSection( + not_null section, + std::shared_ptr image, + TextWithEntities text); + + Section _in; + Section _from; + rpl::variable _active; + + base::unique_qptr _menu; + + std::vector _tabs; + ChatSearchPeerTabType _peerTabType = ChatSearchPeerTabType::Chat; + +}; + +struct FixedHashtagSearchQuery { + QString text; + int cursorPosition = 0; +}; +[[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery( + const QString &query, + int cursorPosition); + +[[nodiscard]] bool IsHashtagSearchQuery(const QString &query); + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp deleted file mode 100644 index 24fc21507..000000000 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include "dialogs/ui/chat_search_tabs.h" - -#include "lang/lang_keys.h" -#include "ui/widgets/discrete_sliders.h" -#include "ui/widgets/shadow.h" -#include "styles/style_dialogs.h" - -namespace Dialogs { -namespace { - -[[nodiscard]] QString TabLabel( - ChatSearchTab tab, - ChatSearchPeerTabType type = {}) { - switch (tab) { - case ChatSearchTab::MyMessages: - return tr::lng_search_tab_my_messages(tr::now); - case ChatSearchTab::ThisTopic: - return tr::lng_search_tab_this_topic(tr::now); - case ChatSearchTab::ThisPeer: - switch (type) { - case ChatSearchPeerTabType::Chat: - return tr::lng_search_tab_this_chat(tr::now); - case ChatSearchPeerTabType::Channel: - return tr::lng_search_tab_this_channel(tr::now); - case ChatSearchPeerTabType::Group: - return tr::lng_search_tab_this_group(tr::now); - } - Unexpected("Type in Dialogs::TabLabel."); - case ChatSearchTab::PublicPosts: - return tr::lng_search_tab_public_posts(tr::now); - } - Unexpected("Tab in Dialogs::TabLabel."); -} - -} // namespace - -TextWithEntities DefaultShortLabel(ChatSearchTab tab) { - // Return them in QString::fromUtf8 format. - switch (tab) { - case ChatSearchTab::MyMessages: - return { QString::fromUtf8("\xf0\x9f\x93\xa8") }; - case ChatSearchTab::PublicPosts: - return { QString::fromUtf8("\xf0\x9f\x8c\x8e") }; - } - Unexpected("Tab in Dialogs::DefaultShortLabel."); -} - -FixedHashtagSearchQuery FixHashtagSearchQuery( - const QString &query, - int cursorPosition) { - const auto trimmed = query.trimmed(); - const auto hash = int(trimmed.isEmpty() - ? query.size() - : query.indexOf(trimmed)); - const auto start = std::min(cursorPosition, hash); - auto result = query.mid(0, start); - for (const auto &ch : query.mid(start)) { - if (ch.isSpace()) { - if (cursorPosition > result.size()) { - --cursorPosition; - } - continue; - } else if (result.size() == start) { - result += '#'; - if (ch != '#') { - ++cursorPosition; - } - } - if (ch != '#') { - result += ch; - } - } - if (result.size() == start) { - result += '#'; - ++cursorPosition; - } - return { result, cursorPosition }; -} - -bool IsHashtagSearchQuery(const QString &query) { - const auto trimmed = query.trimmed(); - if (trimmed.isEmpty() || trimmed[0] != '#') { - return false; - } - for (const auto &ch : trimmed) { - if (ch.isSpace()) { - return false; - } - } - return true; -} - -ChatSearchTabs::ChatSearchTabs( - QWidget *parent, - ChatSearchTab active, - Fn)> markedTextContext) -: RpWidget(parent) -, _tabs(std::make_unique(this, st::dialogsSearchTabs)) -, _shadow(std::make_unique(this)) -, _markedTextContext(std::move(markedTextContext)) -, _active(active) { - _tabs->move(st::dialogsSearchTabsPadding, 0); - _tabs->sectionActivated( - ) | rpl::start_with_next([=](int index) { - _active = _list[index].value; - }, lifetime()); -} - -ChatSearchTabs::~ChatSearchTabs() = default; - -void ChatSearchTabs::setTabShortLabels( - std::vector labels, - ChatSearchTab active, - ChatSearchPeerTabType peerTabType) { - const auto &st = st::dialogsSearchTabs; - const auto &font = st.labelStyle.font; - _list.clear(); - _list.reserve(labels.size()); - - auto widthTotal = 0; - for (const auto tab : { - ChatSearchTab::ThisTopic, - ChatSearchTab::ThisPeer, - ChatSearchTab::MyMessages, - ChatSearchTab::PublicPosts, - }) { - const auto i = ranges::find(labels, tab, &ShortLabel::tab); - if (i != end(labels) && !i->label.empty()) { - const auto label = TabLabel(tab, peerTabType); - const auto widthFull = font->width(label) + st.strictSkip; - _list.push_back({ - .value = tab, - .label = label, - .shortLabel = i->label, - .widthFull = widthFull, - }); - widthTotal += widthFull; - } - } - const auto widthSingleEmoji = st::emojiSize + st.strictSkip; - for (const auto tab : { - ChatSearchTab::PublicPosts, - ChatSearchTab::ThisTopic, - ChatSearchTab::ThisPeer, - ChatSearchTab::MyMessages, - }) { - const auto i = ranges::find(_list, tab, &Tab::value); - if (i != end(_list)) { - i->widthThresholdForShort = widthTotal; - widthTotal -= i->widthFull; - widthTotal += widthSingleEmoji; - } - } - refillTabs(active, width()); -} - -rpl::producer ChatSearchTabs::tabChanges() const { - return _active.changes(); -} - -void ChatSearchTabs::refillTabs( - ChatSearchTab active, - int newWidth) { - auto labels = std::vector(); - const auto available = newWidth - 2 * st::dialogsSearchTabsPadding; - for (const auto &tab : _list) { - auto label = (available < tab.widthThresholdForShort) - ? tab.shortLabel - : TextWithEntities{ tab.label }; - labels.push_back(std::move(label)); - } - _tabs->setSections(labels, _markedTextContext([=] { update(); })); - - const auto i = ranges::find(_list, active, &Tab::value); - Assert(i != end(_list)); - _tabs->setActiveSectionFast(i - begin(_list)); - _tabs->resizeToWidth(newWidth); -} - -int ChatSearchTabs::resizeGetHeight(int newWidth) { - refillTabs(_active.current(), newWidth); - _shadow->setGeometry( - 0, - _tabs->y() + _tabs->height() - st::lineWidth, - newWidth, - st::lineWidth); - return _tabs->height(); -} - -void ChatSearchTabs::paintEvent(QPaintEvent *e) { - QPainter(this).fillRect(e->rect(), st::dialogsBg); -} - -} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h deleted file mode 100644 index 720bf5734..000000000 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h +++ /dev/null @@ -1,89 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#pragma once - -#include "ui/rp_widget.h" - -namespace Ui { -class SettingsSlider; -class PlainShadow; -} // namespace Ui - -namespace Dialogs { - -enum class ChatSearchTab : uchar { - MyMessages, - ThisTopic, - ThisPeer, - PublicPosts, -}; - -enum class ChatSearchPeerTabType : uchar { - Chat, - Channel, - Group, -}; - -// Available for MyMessages and PublicPosts. -[[nodiscard]] TextWithEntities DefaultShortLabel(ChatSearchTab tab); - -class ChatSearchTabs final : public Ui::RpWidget { -public: - ChatSearchTabs( - QWidget *parent, - ChatSearchTab active, - Fn)> markedTextContext); - ~ChatSearchTabs(); - - // A [custom] emoji to use when there is not enough space for text. - // Only tabs with available short labels are shown. - struct ShortLabel { - ChatSearchTab tab = {}; - TextWithEntities label; - }; - void setTabShortLabels( - std::vector labels, - ChatSearchTab active, - ChatSearchPeerTabType peerTabType); - - [[nodiscard]] rpl::producer tabChanges() const; - -private: - struct Tab { - ChatSearchTab value = {}; - QString label; - TextWithEntities shortLabel; - int widthFull = 0; - int widthThresholdForShort = 0; - }; - - void refreshTabs(ChatSearchTab active); - void refillTabs(ChatSearchTab active, int newWidth); - int resizeGetHeight(int newWidth) override; - void paintEvent(QPaintEvent *e) override; - - const std::unique_ptr _tabs; - const std::unique_ptr _shadow; - const Fn)> _markedTextContext; - - std::vector _list; - rpl::variable _active; - -}; - -struct FixedHashtagSearchQuery { - QString text; - int cursorPosition = 0; -}; -[[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery( - const QString &query, - int cursorPosition); - -[[nodiscard]] bool IsHashtagSearchQuery(const QString &query); - -} // namespace Dialogs diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 44644cffe..20748d9fa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -379,6 +379,10 @@ bool Gif::autoplayEnabled() const { _data); } +bool Gif::hideMessageText() const { + return _data->isVideoMessage(); +} + void Gif::draw(Painter &p, const PaintContext &context) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index 840b12587..f0460bf0d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -54,9 +54,7 @@ public: bool spoiler); ~Gif(); - bool hideMessageText() const override { - return false; - } + bool hideMessageText() const override; void draw(Painter &p, const PaintContext &context) const override; TextState textState(QPoint point, StateRequest request) const override; diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp index b9ea1ad0e..fdbd4b69f 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "data/data_photo.h" #include "data/data_photo_media.h" +#include "data/data_session.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_story.h" #include "main/main_session.h" #include "ui/empty_userpic.h" @@ -28,6 +30,8 @@ class PeerUserpic final : public DynamicImage { public: PeerUserpic(not_null peer, bool forceRound); + std::shared_ptr clone() override; + QImage image(int size) override; void subscribeToUpdates(Fn callback) override; @@ -58,7 +62,6 @@ private: class StoryThumbnail : public DynamicImage { public: explicit StoryThumbnail(FullStoryId id); - virtual ~StoryThumbnail() = default; QImage image(int size) override; void subscribeToUpdates(Fn callback) override; @@ -68,6 +71,9 @@ protected: Image *image = nullptr; bool blurred = false; }; + + [[nodiscard]] FullStoryId id() const; + [[nodiscard]] virtual Main::Session &session() = 0; [[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0; virtual void clear() = 0; @@ -85,6 +91,8 @@ class PhotoThumbnail final : public StoryThumbnail { public: PhotoThumbnail(not_null photo, FullStoryId id); + std::shared_ptr clone() override; + private: Main::Session &session() override; Thumb loaded(FullStoryId id) override; @@ -99,6 +107,8 @@ class VideoThumbnail final : public StoryThumbnail { public: VideoThumbnail(not_null video, FullStoryId id); + std::shared_ptr clone() override; + private: Main::Session &session() override; Thumb loaded(FullStoryId id) override; @@ -111,6 +121,8 @@ private: class EmptyThumbnail final : public DynamicImage { public: + std::shared_ptr clone() override; + QImage image(int size) override; void subscribeToUpdates(Fn callback) override; @@ -121,6 +133,8 @@ private: class SavedMessagesUserpic final : public DynamicImage { public: + std::shared_ptr clone() override; + QImage image(int size) override; void subscribeToUpdates(Fn callback) override; @@ -132,6 +146,8 @@ private: class RepliesUserpic final : public DynamicImage { public: + std::shared_ptr clone() override; + QImage image(int size) override; void subscribeToUpdates(Fn callback) override; @@ -141,11 +157,48 @@ private: }; +class IconThumbnail final : public DynamicImage { +public: + explicit IconThumbnail(const style::icon &icon); + + std::shared_ptr clone() override; + + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +private: + const style::icon &_icon; + int _paletteVersion = 0; + QImage _frame; + +}; + +class EmojiThumbnail final : public DynamicImage { +public: + EmojiThumbnail(not_null owner, const QString &data); + + std::shared_ptr clone() override; + + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +private: + const not_null _owner; + const QString _data; + std::unique_ptr _emoji; + QImage _frame; + +}; + PeerUserpic::PeerUserpic(not_null peer, bool forceRound) : _peer(peer) , _forceRound(forceRound) { } +std::shared_ptr PeerUserpic::clone() { + return std::make_shared(_peer, _forceRound); +} + QImage PeerUserpic::image(int size) { Expects(_subscribed != nullptr); @@ -280,11 +333,19 @@ void StoryThumbnail::subscribeToUpdates(Fn callback) { } } +FullStoryId StoryThumbnail::id() const { + return _id; +} + PhotoThumbnail::PhotoThumbnail(not_null photo, FullStoryId id) : StoryThumbnail(id) , _photo(photo) { } +std::shared_ptr PhotoThumbnail::clone() { + return std::make_shared(_photo, id()); +} + Main::Session &PhotoThumbnail::session() { return _photo->session(); } @@ -311,6 +372,10 @@ VideoThumbnail::VideoThumbnail( , _video(video) { } +std::shared_ptr VideoThumbnail::clone() { + return std::make_shared(_video, id()); +} + Main::Session &VideoThumbnail::session() { return _video->session(); } @@ -330,6 +395,10 @@ void VideoThumbnail::clear() { _media = nullptr; } +std::shared_ptr EmptyThumbnail::clone() { + return std::make_shared(); +} + QImage EmptyThumbnail::image(int size) { const auto ratio = style::DevicePixelRatio(); if (_cached.width() != size * ratio) { @@ -345,6 +414,10 @@ QImage EmptyThumbnail::image(int size) { void EmptyThumbnail::subscribeToUpdates(Fn callback) { } +std::shared_ptr SavedMessagesUserpic::clone() { + return std::make_shared(); +} + QImage SavedMessagesUserpic::image(int size) { const auto good = (_frame.width() == size * _frame.devicePixelRatio()); const auto paletteVersion = style::PaletteVersion(); @@ -367,6 +440,13 @@ QImage SavedMessagesUserpic::image(int size) { } void SavedMessagesUserpic::subscribeToUpdates(Fn callback) { + if (!callback) { + _frame = {}; + } +} + +std::shared_ptr RepliesUserpic::clone() { + return std::make_shared(); } QImage RepliesUserpic::image(int size) { @@ -391,6 +471,90 @@ QImage RepliesUserpic::image(int size) { } void RepliesUserpic::subscribeToUpdates(Fn callback) { + if (!callback) { + _frame = {}; + } +} + +IconThumbnail::IconThumbnail(const style::icon &icon) : _icon(icon) { +} + +std::shared_ptr IconThumbnail::clone() { + return std::make_shared(_icon); +} + +QImage IconThumbnail::image(int size) { + const auto good = (_frame.width() == size * _frame.devicePixelRatio()); + const auto paletteVersion = style::PaletteVersion(); + if (!good || _paletteVersion != paletteVersion) { + _paletteVersion = paletteVersion; + + const auto ratio = style::DevicePixelRatio(); + if (!good) { + _frame = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _frame.setDevicePixelRatio(ratio); + } + _frame.fill(Qt::transparent); + + auto p = Painter(&_frame); + _icon.paintInCenter(p, QRect(0, 0, size, size)); + } + return _frame; +} + +void IconThumbnail::subscribeToUpdates(Fn callback) { + if (!callback) { + _frame = {}; + } +} + +EmojiThumbnail::EmojiThumbnail( + not_null owner, + const QString &data) +: _owner(owner) +, _data(data) { +} + +void EmojiThumbnail::subscribeToUpdates(Fn callback) { + if (!callback) { + _emoji = nullptr; + return; + } + _emoji = _owner->customEmojiManager().create( + _data, + std::move(callback), + Data::CustomEmojiSizeTag::Large); +} + +std::shared_ptr EmojiThumbnail::clone() { + return std::make_shared(_owner, _data); +} + +QImage EmojiThumbnail::image(int size) { + Expects(_emoji != nullptr); + + const auto ratio = style::DevicePixelRatio(); + const auto good = (_frame.width() == size * _frame.devicePixelRatio()); + if (!good) { + _frame = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _frame.setDevicePixelRatio(ratio); + } + _frame.fill(Qt::transparent); + + auto p = Painter(&_frame); + _emoji->paint(p, { + .textColor = st::windowBoldFg->c, + .now = crl::now(), + .position = QPoint(0, 0), + .paused = false, + }); + p.end(); + + return _frame; } } // namespace @@ -422,4 +586,14 @@ std::shared_ptr MakeStoryThumbnail( }); } +std::shared_ptr MakeIconThumbnail(const style::icon &icon) { + return std::make_shared(icon); +} + +std::shared_ptr MakeEmojiThumbnail( + not_null owner, + const QString &data) { + return std::make_shared(owner, data); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h index 4b4e0e553..df36ae984 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.h +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h @@ -11,6 +11,7 @@ class PeerData; namespace Data { class Story; +class Session; } // namespace Data namespace Ui { @@ -24,5 +25,10 @@ class DynamicImage; [[nodiscard]] std::shared_ptr MakeRepliesThumbnail(); [[nodiscard]] std::shared_ptr MakeStoryThumbnail( not_null story); +[[nodiscard]] std::shared_ptr MakeIconThumbnail( + const style::icon &icon); +[[nodiscard]] std::shared_ptr MakeEmojiThumbnail( + not_null owner, + const QString &data); } // namespace Ui diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 8f2c0f861..8d08ff7de 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -136,6 +136,7 @@ menuIconGroupCreate: icon {{ "menu/groups_create", menuIconColor }}; menuIconSigned: icon {{ "menu/signed", menuIconColor }}; menuIconAntispam: icon {{ "menu/antispam", menuIconColor }}; menuIconChatDiscuss: icon {{ "menu/chat_discuss", menuIconColor }}; +menuIconChats: icon {{ "menu/chats", menuIconColor }}; menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }}; menuIconPremium: icon {{ "menu/premium", menuIconColor }}; menuIconShop: icon {{ "menu/shop", menuIconColor }}; diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 29a2c10b0..2b69b2df7 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -89,8 +89,8 @@ PRIVATE dialogs/dialogs_three_state_icon.h dialogs/ui/chat_search_empty.cpp dialogs/ui/chat_search_empty.h - dialogs/ui/chat_search_tabs.cpp - dialogs/ui/chat_search_tabs.h + dialogs/ui/chat_search_in.cpp + dialogs/ui/chat_search_in.h dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.h dialogs/ui/top_peers_strip.cpp diff --git a/Telegram/lib_ui b/Telegram/lib_ui index d0a7ff773..7b48c6a36 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit d0a7ff7734b6f887dc6367742c40c50729c032e3 +Subproject commit 7b48c6a3618c7953046914ca4c6fe739684644b8