diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e637796956..086712c3ce 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -568,6 +568,8 @@ PRIVATE history/view/controls/compose_controls_common.h history/view/controls/history_view_compose_controls.cpp history/view/controls/history_view_compose_controls.h + history/view/controls/history_view_compose_search.cpp + history/view/controls/history_view_compose_search.h history/view/controls/history_view_ttl_button.cpp history/view/controls/history_view_ttl_button.h history/view/controls/history_view_voice_record_bar.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0e4ddf2518..9b9e9e1707 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1972,6 +1972,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_search_found_results#other" = "Found {count} messages"; "lng_search_global_results" = "Global search results"; "lng_search_messages_from" = "Show messages from"; +"lng_search_messages_n_of_amount" = "{n} of {amount}"; +"lng_search_messages_none" = "No result"; "lng_media_save_progress" = "{ready} of {total} {mb}"; "lng_mediaview_save_as" = "Save As..."; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp new file mode 100644 index 0000000000..2dcedd422f --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp @@ -0,0 +1,313 @@ +/* +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 "history/view/controls/history_view_compose_search.h" + +#include "api/api_messages_search.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "lang/lang_keys.h" +#include "ui/effects/show_animation.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/multi_select.h" +#include "window/window_session_controller.h" +#include "styles/style_boxes.h" +#include "styles/style_chat.h" +#include "styles/style_dialogs.h" +#include "styles/style_info.h" + +namespace HistoryView { +namespace { + +class TopBar final : public Ui::RpWidget { +public: + TopBar(not_null parent); + + [[nodiscard]] rpl::producer searchRequests() const; + +private: + base::unique_qptr _cancel; + base::unique_qptr _select; + + base::Timer _searchTimer; + + rpl::event_stream _searchRequests; +}; + +TopBar::TopBar(not_null parent) +: Ui::RpWidget(parent) +, _cancel(base::make_unique_q(this, st::historyTopBarBack)) +, _select(base::make_unique_q( + this, + st::searchInChatMultiSelect, + tr::lng_dlg_filter())) +, _searchTimer([=] { _searchRequests.fire(_select->getQuery()); }) { + + parent->geometryValue( + ) | rpl::start_with_next([=](const QRect &r) { + moveToLeft(0, 0); + resize(r.width(), st::topBarHeight); + }, lifetime()); + + sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + _cancel->moveToLeft(0, (s.height() - _cancel->height()) / 2); + + const auto selectLeft = _cancel->x() + _cancel->width(); + _select->resizeToWidth(s.width() - selectLeft); + _select->moveToLeft(selectLeft, (s.height() - _select->height()) / 2); + + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + Painter p(this); + + p.fillRect(r, st::dialogsBg); + }, lifetime()); + + _select->setQueryChangedCallback([=](const QString &query) { + _searchTimer.callOnce(AutoSearchTimeout); + }); + + _select->setSubmittedCallback([=](Qt::KeyboardModifiers) { + _searchRequests.fire(_select->getQuery()); + }); + + _select->setCancelledCallback([=] { + + }); +} + +rpl::producer TopBar::searchRequests() const { + return _searchRequests.events(); +} + +class BottomBar final : public Ui::RpWidget { +public: + using Index = int; + BottomBar(not_null parent); + + void setTotal(int total); + + [[nodiscard]] rpl::producer showItemRequests() const; + +private: + void updateText(int current); + + base::unique_qptr _previous; + base::unique_qptr _next; + base::unique_qptr _counter; + + int _total = -1; + rpl::variable _current = 0; +}; + +BottomBar::BottomBar(not_null parent) +: Ui::RpWidget(parent) +// Icons are swaped. +, _previous(base::make_unique_q(this, st::calendarNext)) +, _next(base::make_unique_q(this, st::calendarPrevious)) +, _counter(base::make_unique_q( + this, + st::defaultSettingsRightLabel)) { + + parent->geometryValue( + ) | rpl::start_with_next([=](const QRect &r) { + const auto height = st::historyComposeButton.height; + resize(r.width(), height); + moveToLeft(0, r.height() - height); + }, lifetime()); + + rpl::merge( + _counter->sizeValue() | rpl::map([=] { return size(); }), + sizeValue() + ) | rpl::start_with_next([=](const QSize &s) { + _previous->moveToRight(0, (s.height() - _previous->height()) / 2); + _next->moveToRight( + _previous->width(), + (s.height() - _next->height()) / 2); + + const auto left = st::topBarActionSkip; + _counter->moveToLeft(left, (s.height() - _counter->height()) / 2); + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + Painter p(this); + + p.fillRect(r, st::dialogsBg); + }, lifetime()); + + _current.value( + ) | rpl::start_with_next([=](int current) { + const auto nextDisabled = (current <= 0) || (current >= _total); + const auto prevDisabled = (current <= 1); + _next->setAttribute(Qt::WA_TransparentForMouseEvents, nextDisabled); + _previous->setAttribute( + Qt::WA_TransparentForMouseEvents, + prevDisabled); + _next->setIconOverride(nextDisabled + ? &st::calendarPreviousDisabled + : nullptr); + _previous->setIconOverride(prevDisabled + ? &st::calendarNextDisabled + : nullptr); + + updateText(current); + }, lifetime()); + + rpl::merge( + _next->clicks() | rpl::map_to(1), + _previous->clicks() | rpl::map_to(-1) + ) | rpl::start_with_next([=](int way) { + _current = _current.current() + way; + }, lifetime()); +} + +void BottomBar::setTotal(int total) { + _total = total; + _current.force_assign(1); +} + +void BottomBar::updateText(int current) { + if (_total < 0) { + _counter->setText(QString()); + } else if (_total) { + _counter->setText(tr::lng_search_messages_n_of_amount( + tr::now, + lt_n, + QString::number(current), + lt_amount, + QString::number(_total))); + } else { + _counter->setText(tr::lng_search_messages_none(tr::now)); + } +} + +rpl::producer BottomBar::showItemRequests() const { + return _current.changes() | rpl::map(rpl::mappers::_1 - 1); +} + +} // namespace + +class ComposeSearch::Inner final { +public: + Inner( + not_null parent, + not_null window, + not_null history); + ~Inner(); + +private: + void showAnimated(); + void hideAnimated(); + + const not_null _window; + const not_null _history; + const base::unique_qptr _topBar; + const base::unique_qptr _bottomBar; + + Api::MessagesSearch _apiSearch; + Api::FoundMessages _apiFound; + + struct { + struct { + QString token; + BottomBar::Index index = -1; + } data; + rpl::event_stream jumps; + } _pendingJump; + +}; + +ComposeSearch::Inner::Inner( + not_null parent, + not_null window, + not_null history) +: _window(window) +, _history(history) +, _topBar(base::make_unique_q(parent)) +, _bottomBar(base::make_unique_q(parent)) +, _apiSearch(&window->session(), history) { + showAnimated(); + + _topBar->searchRequests( + ) | rpl::start_with_next([=](const QString &query) { + if (query.isEmpty()) { + return; + } + _apiFound = {}; + _apiSearch.searchMessages(query, nullptr); + }, _topBar->lifetime()); + + _apiSearch.messagesFounds( + ) | rpl::start_with_next([=](const Api::FoundMessages &data) { + if (data.nextToken == _apiFound.nextToken) { + for (const auto &message : data.messages) { + _apiFound.messages.push_back(message); + } + if (_pendingJump.data.token == data.nextToken) { + _pendingJump.jumps.fire_copy(_pendingJump.data.index); + } + } else { + _apiFound = data; + _bottomBar->setTotal(data.total); + } + }, _topBar->lifetime()); + + rpl::merge( + _pendingJump.jumps.events() | rpl::filter(rpl::mappers::_1 >= 0), + _bottomBar->showItemRequests() + ) | rpl::start_with_next([=](BottomBar::Index index) { + const auto &messages = _apiFound.messages; + const auto size = int(messages.size()); + if (index >= (size - 1) && size != _apiFound.total) { + _apiSearch.searchMore(); + } + if (index >= size || index < 0) { + _pendingJump.data = { _apiFound.nextToken, index }; + return; + } + _pendingJump.data = {}; + const auto itemId = _apiFound.messages[index]; + const auto item = _history->owner().message(itemId); + if (item) { + _window->jumpToChatListEntry({ + { item->history() }, + item->fullId(), + }); + } + }, _bottomBar->lifetime()); +} + +void ComposeSearch::Inner::showAnimated() { + // Don't animate bottom bar. + _bottomBar->show(); + Ui::Animations::ShowWidgets({ _topBar.get() }); +} + +void ComposeSearch::Inner::hideAnimated() { + Ui::Animations::HideWidgets({ _topBar.get(), _bottomBar.get() }); +} + +ComposeSearch::Inner::~Inner() { +} + +ComposeSearch::ComposeSearch( + not_null parent, + not_null window, + not_null history) +: _inner(std::make_unique(parent, window, history)) { +} + +ComposeSearch::~ComposeSearch() { +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.h new file mode 100644 index 0000000000..d630df9d24 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.h @@ -0,0 +1,36 @@ +/* +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 + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { +class RpWidget; +} // namespace Ui + +class History; + +namespace HistoryView { + +class ComposeSearch final { +public: + ComposeSearch( + not_null parent, + not_null window, + not_null history); + ~ComposeSearch(); + +private: + class Inner; + const std::unique_ptr _inner; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 1da848085f..46adcf3f1e 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1001,3 +1001,10 @@ reactionAppearStartSkip: 2px; reactionMainAppearShift: 20px; reactionCollapseFadeThreshold: 40px; reactionFlyUp: 50px; + +searchInChatMultiSelectItem: MultiSelectItem(defaultMultiSelectItem) { + maxWidth: 200px; +} +searchInChatMultiSelect: MultiSelect(defaultMultiSelect) { + item: searchInChatMultiSelectItem; +} diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 8700c2223a..a0b04da1d9 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 8700c2223ab60db994376b39e4e74a6309d5154a +Subproject commit a0b04da1d95a67d1ba431b7652026bd72d86962e