From 77939ae9bd64ce76865e68115b8a402aab1677bf Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 Apr 2023 16:16:38 +0400 Subject: [PATCH] Add more-chats-added bar to cloud folders. --- .../Resources/animations/cloud_filters.tgs | Bin 0 -> 9372 bytes .../Resources/qrc/telegram/animations.qrc | 1 + Telegram/SourceFiles/api/api_chat_filters.cpp | 52 ++-- Telegram/SourceFiles/api/api_chat_filters.h | 5 + .../boxes/filters/edit_filter_links.cpp | 34 ++- .../SourceFiles/data/data_chat_filters.cpp | 126 +++++++++- Telegram/SourceFiles/data/data_chat_filters.h | 25 ++ .../SourceFiles/dialogs/dialogs_widget.cpp | 60 ++++- Telegram/SourceFiles/dialogs/dialogs_widget.h | 4 + Telegram/SourceFiles/ui/chat/chat.style | 18 ++ .../SourceFiles/ui/chat/more_chats_bar.cpp | 223 ++++++++++++++++++ Telegram/SourceFiles/ui/chat/more_chats_bar.h | 73 ++++++ Telegram/cmake/td_ui.cmake | 2 + 13 files changed, 599 insertions(+), 24 deletions(-) create mode 100644 Telegram/Resources/animations/cloud_filters.tgs create mode 100644 Telegram/SourceFiles/ui/chat/more_chats_bar.cpp create mode 100644 Telegram/SourceFiles/ui/chat/more_chats_bar.h diff --git a/Telegram/Resources/animations/cloud_filters.tgs b/Telegram/Resources/animations/cloud_filters.tgs new file mode 100644 index 0000000000000000000000000000000000000000..3e71d25b4086c5c420c09f6373579e7775cdeeda GIT binary patch literal 9372 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 W&rP%L&{E;~um2Aa!?@kjyZ``a=M2^W literal 0 HcmV?d00001 diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 06a600c00..01ab9a5f7 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -3,6 +3,7 @@ ../../animations/change_number.tgs ../../animations/blocked_peers_empty.tgs ../../animations/filters.tgs + ../../animations/cloud_filters.tgs ../../animations/local_passcode_enter.tgs ../../animations/cloud_password/intro.tgs ../../animations/cloud_password/password_input.tgs diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index 7908ddc40..1e1476b11 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -191,35 +191,39 @@ void InitFilterLinkHeader( } void ImportInvite( - base::weak_ptr weak, const QString &slug, + FilterId filterId, const base::flat_set> &peers, Fn done, - Fn fail) { + Fn fail) { Expects(!peers.empty()); const auto peer = peers.front(); const auto api = &peer->session().api(); const auto callback = [=](const MTPUpdates &result) { api->applyUpdates(result); + if (slug.isEmpty()) { + peer->owner().chatsFilters().moreChatsHide(filterId, true); + } done(); }; const auto error = [=](const MTP::Error &error) { - if (const auto strong = weak.get()) { - Ui::ShowMultilineToast({ - .parentOverride = Window::Show(strong).toastParent(), - .text = { error.type() }, - }); - } - fail(); + fail(error.type()); }; auto inputs = peers | ranges::views::transform([](auto peer) { return MTPInputPeer(peer->input); }) | ranges::to(); - api->request(MTPchatlists_JoinChatlistInvite( - MTP_string(slug), - MTP_vector(std::move(inputs)) - )).done(callback).fail(error).send(); + if (!slug.isEmpty()) { + api->request(MTPchatlists_JoinChatlistInvite( + MTP_string(slug), + MTP_vector(std::move(inputs)) + )).done(callback).fail(error).send(); + } else { + api->request(MTPchatlists_JoinChatlistUpdates( + MTP_inputChatlistDialogFilter(MTP_int(filterId)), + MTP_vector(std::move(inputs)) + )).done(callback).fail(error).send(); + } } ToggleChatsController::ToggleChatsController( @@ -462,10 +466,17 @@ void ProcessFilterInvite( // #TODO filters } else if (!state->importing) { state->importing = true; - ImportInvite(weak, slug, peers, crl::guard(box, [=] { + ImportInvite(slug, filterId, peers, crl::guard(box, [=] { ShowImportToast(weak, title, type, peers.size()); box->closeBox(); - }), crl::guard(box, [=] { + }), crl::guard(box, [=](QString text) { + if (const auto strong = weak.get()) { + Ui::ShowMultilineToast({ + .parentOverride = Window::Show( + strong).toastParent(), + .text = { text }, + }); + } state->importing = false; })); } @@ -604,6 +615,17 @@ void CheckFilterInvite( }); } +void ProcessFilterUpdate( + base::weak_ptr weak, + FilterId filterId, + std::vector> missing) { + if (const auto strong = missing.empty() ? weak.get() : nullptr) { + strong->session().data().chatsFilters().moreChatsHide(filterId); + return; + } + ProcessFilterInvite(weak, QString(), filterId, std::move(missing), {}); +} + void ProcessFilterRemove( base::weak_ptr weak, const QString &title, diff --git a/Telegram/SourceFiles/api/api_chat_filters.h b/Telegram/SourceFiles/api/api_chat_filters.h index 7691d899b..c9d7e6732 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.h +++ b/Telegram/SourceFiles/api/api_chat_filters.h @@ -25,6 +25,11 @@ void CheckFilterInvite( not_null controller, const QString &slug); +void ProcessFilterUpdate( + base::weak_ptr weak, + FilterId filterId, + std::vector> missing); + void ProcessFilterRemove( base::weak_ptr weak, const QString &title, diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp index c750c47f8..15ee8c07f 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp @@ -102,13 +102,19 @@ struct Errors { Unexpected("Peer type in ErrorForSharing."); } -void ShowEmptyLinkError(not_null window) { +void ShowSaveError( + not_null window, + QString error) { Ui::ShowMultilineToast({ .parentOverride = Window::Show(window).toastParent(), - .text = { tr::lng_filters_empty(tr::now) }, + .text = { error }, }); } +void ShowEmptyLinkError(not_null window) { + ShowSaveError(window, tr::lng_filters_empty(tr::now)); +} + void ChatFilterLinkBox( not_null box, not_null session, @@ -545,7 +551,7 @@ void LinkController::addHeader(not_null container) { auto icon = CreateLottieIcon( verticalLayout, { - .name = u"filters"_q, + .name = u"cloud_filters"_q, .sizeOverride = { st::settingsFilterIconSize, st::settingsFilterIconSize, @@ -1084,7 +1090,8 @@ void ExportFilterLink( void EditLinkChats( const Data::ChatFilterLink &link, - base::flat_set> peers) { + base::flat_set> peers, + Fn done) { Expects(!peers.empty()); Expects(link.id != 0); Expects(!link.url.isEmpty()); @@ -1104,9 +1111,9 @@ void EditLinkChats( )).done([=](const MTPExportedChatlistInvite &result) { const auto &data = result.data(); const auto link = session->data().chatsFilters().add(id, result); - //done(link); + done(QString()); }).fail([=](const MTP::Error &error) { - //done({ .id = id }); + done(error.type()); }).send(); } @@ -1122,6 +1129,7 @@ object_ptr ShowLinkBox( ? rpl::single(link.title) : tr::lng_filters_link_title()); + const auto saving = std::make_shared(false); raw->hasChangesValue( ) | rpl::start_with_next([=](bool has) { box->setCloseByOutsideClick(!has); @@ -1129,11 +1137,23 @@ object_ptr ShowLinkBox( box->clearButtons(); if (has) { box->addButton(tr::lng_settings_save(), [=] { + if (*saving) { + return; + } const auto chosen = raw->selected(); if (chosen.empty()) { ShowEmptyLinkError(window); } else { - EditLinkChats(link, chosen); + *saving = true; + EditLinkChats(link, chosen, crl::guard(box, [=]( + QString error) { + *saving = false; + if (error.isEmpty()) { + box->closeBox(); + } else { + ShowSaveError(window, error); + } + })); } }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index e70665205..9d815ca18 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_unread_things.h" #include "ui/ui_utility.h" +#include "ui/chat/more_chats_bar.h" #include "main/main_session.h" #include "main/main_account.h" #include "main/main_app_config.h" @@ -31,6 +32,12 @@ constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000); constexpr auto kLoadExceptionsAfter = 100; constexpr auto kLoadExceptionsPerRequest = 100; +[[nodiscard]] crl::time RequestUpdatesEach(not_null owner) { + const auto appConfig = &owner->session().account().appConfig(); + return appConfig->get(u"chatlist_update_period"_q, 3600) + * crl::time(1000); +} + } // namespace ChatFilter::ChatFilter( @@ -287,7 +294,9 @@ bool ChatFilter::contains(not_null history) const { || _always.contains(history); } -ChatFilters::ChatFilters(not_null owner) : _owner(owner) { +ChatFilters::ChatFilters(not_null owner) +: _owner(owner) +, _moreChatsTimer([=] { checkLoadMoreChatsLists(); }) { _list.emplace_back(); crl::on_main(&owner->session(), [=] { load(); }); } @@ -855,4 +864,119 @@ rpl::producer<> ChatFilters::suggestedUpdated() const { return _suggestedUpdated.events(); } +rpl::producer ChatFilters::moreChatsContent( + FilterId id) { + if (!id) { + return rpl::single(Ui::MoreChatsBarContent{ .count = 0 }); + } + return [=](auto consumer) { + auto result = rpl::lifetime(); + + auto &entry = _moreChatsData[id]; + auto watching = entry.watching.lock(); + if (!watching) { + watching = std::make_shared(true); + entry.watching = watching; + } + result.add([watching] {}); + + _moreChatsUpdated.events_starting_with_copy( + id + ) | rpl::start_with_next([=] { + consumer.put_next(Ui::MoreChatsBarContent{ + .count = int(moreChats(id).size()), + }); + }, result); + loadMoreChatsList(id); + + return result; + }; +} + +const std::vector> &ChatFilters::moreChats( + FilterId id) const { + static const auto kEmpty = std::vector>(); + if (!id) { + return kEmpty; + } + const auto i = _moreChatsData.find(id); + return (i != end(_moreChatsData)) ? i->second.missing : kEmpty; +} + +void ChatFilters::moreChatsHide(FilterId id, bool localOnly) { + if (!localOnly) { + const auto api = &_owner->session().api(); + api->request(MTPchatlists_HideChatlistUpdates( + MTP_inputChatlistDialogFilter(MTP_int(id)) + )).send(); + } + + const auto i = _moreChatsData.find(id); + if (i != end(_moreChatsData)) { + if (const auto requestId = base::take(i->second.requestId)) { + _owner->session().api().request(requestId).cancel(); + } + i->second.missing = {}; + i->second.lastUpdate = crl::now(); + _moreChatsUpdated.fire_copy(id); + } +} + +void ChatFilters::loadMoreChatsList(FilterId id) { + Expects(id != 0); + + const auto i = ranges::find(_list, id, &ChatFilter::id); + if (i == end(_list) || !i->chatlist()) { + return; + } + + auto &entry = _moreChatsData[id]; + const auto now = crl::now(); + if (!entry.watching.lock() || entry.requestId) { + return; + } + const auto last = entry.lastUpdate; + const auto next = last ? (last + RequestUpdatesEach(_owner)) : 0; + if (next > now) { + if (!_moreChatsTimer.isActive()) { + _moreChatsTimer.callOnce(next - now); + } + return; + } + auto &api = _owner->session().api(); + entry.requestId = api.request(MTPchatlists_GetChatlistUpdates( + MTP_inputChatlistDialogFilter(MTP_int(id)) + )).done([=](const MTPchatlists_ChatlistUpdates &result) { + const auto &data = result.data(); + _owner->processUsers(data.vusers()); + _owner->processChats(data.vchats()); + auto list = ranges::views::all( + data.vmissing_peers().v + ) | ranges::views::transform([&](const MTPPeer &peer) { + return _owner->peer(peerFromMTP(peer)); + }) | ranges::to_vector; + + auto &entry = _moreChatsData[id]; + entry.requestId = 0; + entry.lastUpdate = crl::now(); + if (!_moreChatsTimer.isActive()) { + _moreChatsTimer.callOnce(RequestUpdatesEach(_owner)); + } + if (entry.missing != list) { + entry.missing = std::move(list); + _moreChatsUpdated.fire_copy(id); + } + }).fail([=] { + auto &entry = _moreChatsData[id]; + entry.requestId = 0; + entry.lastUpdate = crl::now(); + }).send(); +} + +void ChatFilters::checkLoadMoreChatsLists() { + for (const auto &[id, entry] : _moreChatsData) { + loadMoreChatsList(id); + } +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index f07ec5a1b..024240061 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/flags.h" +#include "base/timer.h" class History; @@ -16,6 +17,10 @@ class MainList; class Key; } // namespace Dialogs +namespace Ui { +struct MoreChatsBarContent; +} // namespace Ui + namespace Data { class Session; @@ -159,7 +164,20 @@ public: FilterId id) const; void reloadChatlistLinks(FilterId id); + [[nodiscard]] rpl::producer moreChatsContent( + FilterId id); + [[nodiscard]] const std::vector> &moreChats( + FilterId id) const; + void moreChatsHide(FilterId id, bool localOnly = false); + private: + struct MoreChatsData { + std::vector> missing; + crl::time lastUpdate = 0; + mtpRequestId requestId = 0; + std::weak_ptr watching; + }; + void load(bool force); void received(const QVector &list); bool applyOrder(const QVector &order); @@ -167,6 +185,9 @@ private: void applyInsert(ChatFilter filter, int position); void applyRemove(int position); + void checkLoadMoreChatsLists(); + void loadMoreChatsList(FilterId id); + const not_null _owner; std::vector _list; @@ -190,6 +211,10 @@ private: rpl::event_stream _chatlistLinksUpdated; mtpRequestId _linksRequestId = 0; + base::flat_map _moreChatsData; + rpl::event_stream _moreChatsUpdated; + base::Timer _moreChatsTimer; + }; } // namespace Data diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 91e5ac701..9f4522e47 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/radial_animation.h" #include "ui/chat/requests_bar.h" #include "ui/chat/group_call_bar.h" +#include "ui/chat/more_chats_bar.h" #include "ui/controls/download_bar.h" #include "ui/controls/jump_down_button.h" #include "ui/painter.h" @@ -34,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_domain.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "api/api_chat_filters.h" #include "apiwrap.h" #include "base/event_filter.h" #include "core/application.h" @@ -440,6 +442,7 @@ Widget::Widget( _searchForNarrowFilters->setRippleColorOverride(color); }, lifetime()); + setupMoreChatsBar(); setupDownloadBar(); } } @@ -542,6 +545,49 @@ void Widget::setupScrollUpButton() { updateScrollUpVisibility(); } +void Widget::setupMoreChatsBar() { + if (_layout == Layout::Child) { + return; + } + controller()->activeChatsFilter( + ) | rpl::start_with_next([=](FilterId id) { + if (!id) { + _moreChatsBar = nullptr; + updateControlsGeometry(); + return; + } + const auto filters = &session().data().chatsFilters(); + _moreChatsBar = std::make_unique( + this, + filters->moreChatsContent(id)); + + _moreChatsBar->barClicks( + ) | rpl::start_with_next([=] { + if (const auto missing = filters->moreChats(id) + ; !missing.empty()) { + Api::ProcessFilterUpdate(controller(), id, missing); + } + }, _moreChatsBar->lifetime()); + + _moreChatsBar->closeClicks( + ) | rpl::start_with_next([=] { + Api::ProcessFilterUpdate(controller(), id, {}); + }, _moreChatsBar->lifetime()); + + if (_showAnimation) { + _moreChatsBar->hide(); + } else { + _moreChatsBar->show(); + _moreChatsBar->finishAnimating(); + } + + _moreChatsBar->heightValue( + ) | rpl::start_with_next([=] { + updateControlsGeometry(); + }, _moreChatsBar->lifetime()); + }, lifetime()); +} + void Widget::setupDownloadBar() { if (_layout == Layout::Child) { return; @@ -735,6 +781,9 @@ void Widget::updateControlsVisibility(bool fast) { _updateTelegram->show(); } _searchControls->setVisible(!_openedFolder && !_openedForum); + if (_moreChatsBar) { + _moreChatsBar->show(); + } if (_openedFolder || _openedForum) { _subsectionTopBar->show(); if (_forumTopShadow) { @@ -1165,6 +1214,9 @@ void Widget::startSlideAnimation( if (_subsectionTopBar) { _subsectionTopBar->hide(); } + if (_moreChatsBar) { + _moreChatsBar->hide(); + } if (_forumTopShadow) { _forumTopShadow->hide(); } @@ -2417,7 +2469,13 @@ void Widget::updateControlsGeometry() { barw, st::lineWidth); } - const auto forumGroupCallTop = filterAreaTop + filterAreaHeight; + const auto moreChatsBarTop = filterAreaTop + filterAreaHeight; + if (_moreChatsBar) { + _moreChatsBar->move(0, moreChatsBarTop); + _moreChatsBar->resizeToWidth(barw); + } + const auto forumGroupCallTop = moreChatsBarTop + + (_moreChatsBar ? _moreChatsBar->height() : 0); if (_forumGroupCallBar) { _forumGroupCallBar->move(0, forumGroupCallTop); _forumGroupCallBar->resizeToWidth(barw); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index b33dce8fd..009f6ca3a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -44,6 +44,7 @@ class PlainShadow; class DownloadBar; class GroupCallBar; class RequestsBar; +class MoreChatsBar; class JumpDownButton; template class FadeWrapScaled; @@ -158,6 +159,7 @@ private: void setupSupportMode(); void setupConnectingWidget(); void setupMainMenuToggle(); + void setupMoreChatsBar(); void setupDownloadBar(); void setupShortcuts(); [[nodiscard]] bool searchForPeersRequired(const QString &query) const; @@ -234,6 +236,8 @@ private: object_ptr _cancelSearch; object_ptr _lockUnlock; + std::unique_ptr _moreChatsBar; + std::unique_ptr _forumTopShadow; std::unique_ptr _forumGroupCallBar; std::unique_ptr _forumRequestsBar; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index b62b92ebf..a60ee8383 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1281,3 +1281,21 @@ historySendDisabled: FlatLabel(defaultFlatLabel) { historySendDisabledIcon: icon {{ "emoji/premium_lock", placeholderFgActive }}; historySendDisabledIconSkip: 20px; historySendDisabledPosition: point(0px, 0px); + +moreChatsBarHeight: 48px; +moreChatsBarTextPosition: point(12px, 4px); +moreChatsBarStatusPosition: point(12px, 24px); +moreChatsBarClose: IconButton(defaultIconButton) { + width: 48px; + height: 48px; + + icon: boxTitleCloseIcon; + iconOver: boxTitleCloseIconOver; + iconPosition: point(12px, -1px); + + rippleAreaPosition: point(0px, 4px); + rippleAreaSize: 40px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} diff --git a/Telegram/SourceFiles/ui/chat/more_chats_bar.cpp b/Telegram/SourceFiles/ui/chat/more_chats_bar.cpp new file mode 100644 index 000000000..e645801a2 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/more_chats_bar.cpp @@ -0,0 +1,223 @@ +/* +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 "ui/chat/more_chats_bar.h" + +#include "ui/widgets/buttons.h" +#include "ui/widgets/shadow.h" +#include "ui/text/text_options.h" +#include "ui/painter.h" +#include "lang/lang_keys.h" +#include "styles/style_chat.h" +#include "styles/style_window.h" // st::columnMinimalWidthLeft + +namespace Ui { + +MoreChatsBar::MoreChatsBar( + not_null parent, + rpl::producer content) +: _wrap(parent, object_ptr(parent)) +, _inner(_wrap.entity()) +, _shadow(std::make_unique(_wrap.parentWidget())) +, _close(_inner.get(), st::moreChatsBarClose) { + _wrap.hide(anim::type::instant); + _shadow->hide(); + + _wrap.entity()->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg); + }, lifetime()); + _wrap.setAttribute(Qt::WA_OpaquePaintEvent); + + auto copy = std::move( + content + ) | rpl::start_spawning(_wrap.lifetime()); + + rpl::duplicate( + copy + ) | rpl::start_with_next([=](MoreChatsBarContent &&content) { + _content = content; + if (_content.count > 0) { + _text.setText( + st::defaultMessageBar.title, + tr::lng_filters_bar_you_can( + tr::now, + lt_count, + _content.count), + Ui::NameTextOptions()); + _status.setText( + st::defaultMessageBar.text, + tr::lng_filters_bar_view( + tr::now, + lt_count, + _content.count), + Ui::NameTextOptions()); + } + _inner->update(); + }, lifetime()); + + std::move( + copy + ) | rpl::map([=](const MoreChatsBarContent &content) { + return !content.count; + }) | rpl::start_with_next_done([=](bool hidden) { + _shouldBeShown = !hidden; + if (!_forceHidden) { + _wrap.toggle(_shouldBeShown, anim::type::normal); + } + }, [=] { + _forceHidden = true; + _wrap.toggle(false, anim::type::normal); + }, lifetime()); + + setupInner(); +} + +MoreChatsBar::~MoreChatsBar() = default; + +void MoreChatsBar::setupInner() { + _inner->resize(0, st::moreChatsBarHeight); + _inner->paintRequest( + ) | rpl::start_with_next([=](QRect rect) { + auto p = Painter(_inner); + paint(p); + }, _inner->lifetime()); + + // Clicks. + _inner->setCursor(style::cur_pointer); + _inner->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonPress); + }) | rpl::map([=] { + return _inner->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonRelease); + }) | rpl::take(1) | rpl::filter([=](not_null event) { + return _inner->rect().contains( + static_cast(event.get())->pos()); + }); + }) | rpl::flatten_latest( + ) | rpl::to_empty | rpl::start_to_stream(_barClicks, _inner->lifetime()); + + _wrap.geometryValue( + ) | rpl::start_with_next([=](QRect rect) { + updateShadowGeometry(rect); + updateControlsGeometry(rect); + }, _inner->lifetime()); +} + +void MoreChatsBar::paint(Painter &p) { + p.fillRect(_inner->rect(), st::historyComposeAreaBg); + + const auto width = std::max( + _inner->width(), + st::columnMinimalWidthLeft); + const auto available = width + - st::moreChatsBarTextPosition.x() + - st::moreChatsBarClose.width; + + p.setPen(st::defaultMessageBar.titleFg); + _text.drawElided( + p, + st::moreChatsBarTextPosition.x(), + st::moreChatsBarTextPosition.y(), + available); + + p.setPen(st::defaultMessageBar.textFg); + _status.drawElided( + p, + st::moreChatsBarStatusPosition.x(), + st::moreChatsBarStatusPosition.y(), + available); +} + +void MoreChatsBar::updateControlsGeometry(QRect wrapGeometry) { + const auto hidden = _wrap.isHidden() || !wrapGeometry.height(); + if (_shadow->isHidden() != hidden) { + _shadow->setVisible(!hidden); + } + const auto width = std::max( + wrapGeometry.width(), + st::columnMinimalWidthLeft); + _close->move(width - _close->width(), 0); +} + +void MoreChatsBar::setShadowGeometryPostprocess(Fn postprocess) { + _shadowGeometryPostprocess = std::move(postprocess); + updateShadowGeometry(_wrap.geometry()); +} + +void MoreChatsBar::updateShadowGeometry(QRect wrapGeometry) { + const auto regular = QRect( + wrapGeometry.x(), + wrapGeometry.y() + wrapGeometry.height(), + wrapGeometry.width(), + st::lineWidth); + _shadow->setGeometry(_shadowGeometryPostprocess + ? _shadowGeometryPostprocess(regular) + : regular); +} + +void MoreChatsBar::show() { + if (!_forceHidden) { + return; + } + _forceHidden = false; + if (_shouldBeShown) { + _wrap.show(anim::type::instant); + _shadow->show(); + } +} + +void MoreChatsBar::hide() { + if (_forceHidden) { + return; + } + _forceHidden = true; + _wrap.hide(anim::type::instant); + _shadow->hide(); +} + +void MoreChatsBar::raise() { + _wrap.raise(); + _shadow->raise(); +} + +void MoreChatsBar::finishAnimating() { + _wrap.finishAnimating(); +} + +void MoreChatsBar::move(int x, int y) { + _wrap.move(x, y); +} + +void MoreChatsBar::resizeToWidth(int width) { + _wrap.entity()->resizeToWidth(width); + _inner->resizeToWidth(width); +} + +int MoreChatsBar::height() const { + return !_forceHidden + ? _wrap.height() + : _shouldBeShown + ? st::moreChatsBarHeight + : 0; +} + +rpl::producer MoreChatsBar::heightValue() const { + return _wrap.heightValue(); +} + +rpl::producer<> MoreChatsBar::barClicks() const { + return _barClicks.events(); +} + +rpl::producer<> MoreChatsBar::closeClicks() const { + return _close->clicks() | rpl::to_empty; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/more_chats_bar.h b/Telegram/SourceFiles/ui/chat/more_chats_bar.h new file mode 100644 index 000000000..5536349c8 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/more_chats_bar.h @@ -0,0 +1,73 @@ +/* +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/wrap/slide_wrap.h" +#include "ui/effects/animations.h" +#include "ui/text/text.h" +#include "base/object_ptr.h" +#include "base/timer.h" + +class Painter; + +namespace Ui { + +class PlainShadow; +class IconButton; + +struct MoreChatsBarContent { + int count = 0; +}; + +class MoreChatsBar final { +public: + MoreChatsBar( + not_null parent, + rpl::producer content); + ~MoreChatsBar(); + + void show(); + void hide(); + void raise(); + void finishAnimating(); + + void setShadowGeometryPostprocess(Fn postprocess); + + void move(int x, int y); + void resizeToWidth(int width); + [[nodiscard]] int height() const; + [[nodiscard]] rpl::producer heightValue() const; + [[nodiscard]] rpl::producer<> barClicks() const; + [[nodiscard]] rpl::producer<> closeClicks() const; + + [[nodiscard]] rpl::lifetime &lifetime() { + return _wrap.lifetime(); + } + +private: + void updateShadowGeometry(QRect wrapGeometry); + void updateControlsGeometry(QRect wrapGeometry); + void setupInner(); + void paint(Painter &p); + + SlideWrap<> _wrap; + not_null _inner; + std::unique_ptr _shadow; + object_ptr _close; + rpl::event_stream<> _barClicks; + Fn _shadowGeometryPostprocess; + bool _shouldBeShown = false; + bool _forceHidden = false; + + MoreChatsBarContent _content; + Ui::Text::String _text; + Ui::Text::String _status; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index cae79a558..404f72080 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -222,6 +222,8 @@ PRIVATE ui/chat/message_bar.h ui/chat/message_bubble.cpp ui/chat/message_bubble.h + ui/chat/more_chats_bar.cpp + ui/chat/more_chats_bar.h ui/chat/pinned_bar.cpp ui/chat/pinned_bar.h ui/chat/requests_bar.cpp