diff --git a/Telegram/Resources/animations/cloud_filters.tgs b/Telegram/Resources/animations/cloud_filters.tgs new file mode 100644 index 000000000..3e71d25b4 Binary files /dev/null and b/Telegram/Resources/animations/cloud_filters.tgs differ 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