From df45edd816d29877838f08a9e139892ba57e104e Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 16 Nov 2024 20:46:30 +0400 Subject: [PATCH] Move channel requests to a layer. --- Telegram/CMakeLists.txt | 2 + .../boxes/peers/edit_peer_requests_box.cpp | 125 ++++++-- .../boxes/peers/edit_peer_requests_box.h | 33 +- Telegram/SourceFiles/info/info_controller.cpp | 2 + Telegram/SourceFiles/info/info_controller.h | 1 + Telegram/SourceFiles/info/info_memento.cpp | 3 + .../info_requests_list_widget.cpp | 287 ++++++++++++++++++ .../requests_list/info_requests_list_widget.h | 68 +++++ 8 files changed, 498 insertions(+), 23 deletions(-) create mode 100644 Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp create mode 100644 Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index aa61a3336..d5d3b2cac 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -986,6 +986,8 @@ PRIVATE info/profile/info_profile_values.h info/profile/info_profile_widget.cpp info/profile/info_profile_widget.h + info/requests_list/info_requests_list_widget.cpp + info/requests_list/info_requests_list_widget.h info/saved/info_saved_sublists_widget.cpp info/saved/info_saved_sublists_widget.h info/settings/info_settings_widget.cpp diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp index c28847bcc..26310db43 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp @@ -7,27 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/edit_peer_requests_box.h" -#include "ui/effects/ripple_animation.h" +#include "api/api_invite_links.h" +#include "apiwrap.h" +#include "base/unixtime.h" #include "boxes/peer_list_controllers.h" #include "boxes/peers/edit_participants_box.h" // SubscribeToMigration #include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus -#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox -#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit -#include "data/data_peer.h" -#include "data/data_user.h" -#include "data/data_chat.h" +#include "boxes/peers/edit_peer_requests_box.h" #include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_peer.h" #include "data/data_session.h" -#include "base/unixtime.h" +#include "data/data_user.h" +#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit +#include "info/info_controller.h" +#include "info/info_memento.h" +#include "info/requests_list/info_requests_list_widget.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "mtproto/sender.h" +#include "ui/effects/ripple_animation.h" +#include "ui/painter.h" #include "ui/round_rect.h" #include "ui/text/text_utilities.h" -#include "ui/painter.h" -#include "lang/lang_keys.h" #include "window/window_session_controller.h" -#include "apiwrap.h" -#include "api/api_invite_links.h" #include "styles/style_boxes.h" namespace { @@ -262,14 +265,10 @@ RequestsBoxController::~RequestsBoxController() = default; void RequestsBoxController::Start( not_null navigation, not_null peer) { - auto controller = std::make_unique( - navigation, - peer->migrateToOrMe()); - const auto initBox = [=](not_null box) { - box->addButton(tr::lng_close(), [=] { box->closeBox(); }); - }; - navigation->parentController()->show( - Box(std::move(controller), initBox)); + navigation->showSection( + std::make_shared( + peer->migrateToOrMe(), + Info::Section::Type::RequestsList)); } Main::Session &RequestsBoxController::session() const { @@ -289,6 +288,58 @@ std::unique_ptr RequestsBoxController::createSearchRow( return nullptr; } +std::unique_ptr RequestsBoxController::createRestoredRow( + not_null peer) { + if (const auto user = peer->asUser()) { + return createRow(user, _dates[user]); + } + return nullptr; +} + +auto RequestsBoxController::saveState() const +-> std::unique_ptr { + auto result = PeerListController::saveState(); + + auto my = std::make_unique(); + my->dates = _dates; + my->offsetDate = _offsetDate; + my->offsetUser = _offsetUser; + my->allLoaded = _allLoaded; + my->wasLoading = (_loadRequestId != 0); + if (const auto search = searchController()) { + my->searchState = search->saveState(); + } + result->controllerState = std::move(my); + return result; +} + +void RequestsBoxController::restoreState( + std::unique_ptr state) { + auto typeErasedState = state + ? state->controllerState.get() + : nullptr; + if (const auto my = dynamic_cast(typeErasedState)) { + if (const auto requestId = base::take(_loadRequestId)) { + _api.request(requestId).cancel(); + } + _dates = std::move(my->dates); + _offsetDate = my->offsetDate; + _offsetUser = my->offsetUser; + _allLoaded = my->allLoaded; + if (const auto search = searchController()) { + search->restoreState(std::move(my->searchState)); + } + if (my->wasLoading) { + loadMoreRows(); + } + PeerListController::restoreState(std::move(state)); + if (delegate()->peerListFullRowsCount() || _allLoaded) { + refreshDescription(); + delegate()->peerListRefreshRows(); + } + } +} + void RequestsBoxController::prepare() { delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetTitle(_peer->isBroadcast() @@ -356,9 +407,7 @@ void RequestsBoxController::refreshDescription() { } void RequestsBoxController::rowClicked(not_null row) { - _navigation->parentController()->show(PrepareShortInfoBox( - row->peer(), - _navigation)); + _navigation->showPeerInfo(row->peer()); } void RequestsBoxController::rowElementClicked( @@ -405,6 +454,7 @@ void RequestsBoxController::appendRow( not_null user, TimeId date) { if (!delegate()->peerListFindRow(user->id.value)) { + _dates.emplace(user, date); if (auto row = createRow(user, date)) { delegate()->peerListAppendRow(std::move(row)); setDescriptionText(QString()); @@ -503,6 +553,7 @@ std::unique_ptr RequestsBoxController::createRow( const auto search = static_cast( searchController()); date = search->dateForUser(user); + _dates.emplace(user, date); } return std::make_unique(_helper.get(), user, date); } @@ -574,6 +625,36 @@ TimeId RequestsBoxSearchController::dateForUser(not_null user) { return {}; } +auto RequestsBoxSearchController::saveState() const +-> std::unique_ptr { + auto result = std::make_unique(); + result->query = _query; + result->offsetDate = _offsetDate; + result->offsetUser = _offsetUser; + result->allLoaded = _allLoaded; + result->wasLoading = (_requestId != 0); + return result; +} + +void RequestsBoxSearchController::restoreState( + std::unique_ptr state) { + if (auto my = dynamic_cast(state.get())) { + if (auto requestId = base::take(_requestId)) { + _api.request(requestId).cancel(); + } + _cache.clear(); + _queries.clear(); + + _allLoaded = my->allLoaded; + _offsetDate = my->offsetDate; + _offsetUser = my->offsetUser; + _query = my->query; + if (my->wasLoading) { + searchOnServer(); + } + } +} + bool RequestsBoxSearchController::searchInCache() { const auto i = _cache.find(_query); if (i != _cache.cend()) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h index 005b83c84..834ec5431 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h @@ -35,15 +35,34 @@ public: Main::Session &session() const override; void prepare() override; void rowClicked(not_null row) override; - void rowElementClicked(not_null row, int element) override; + void rowElementClicked( + not_null row, + int element) override; void loadMoreRows() override; std::unique_ptr createSearchRow( not_null peer) override; + std::unique_ptr createRestoredRow( + not_null peer) override; + + std::unique_ptr saveState() const override; + void restoreState(std::unique_ptr state) override; private: class RowHelper; + struct SavedState : SavedStateBase { + using SearchStateBase = PeerListSearchController::SavedStateBase; + std::unique_ptr searchState; + base::flat_map, TimeId> dates; + TimeId offsetDate = 0; + UserData *offsetUser = nullptr; + bool allLoaded = false; + bool wasLoading = false; + rpl::lifetime lifetime; + + }; + static std::unique_ptr CreateSearchController( not_null peer); @@ -63,6 +82,8 @@ private: not_null _peer; MTP::Sender _api; + base::flat_map, TimeId> _dates; + TimeId _offsetDate = 0; UserData *_offsetUser = nullptr; mtpRequestId _loadRequestId = 0; @@ -82,7 +103,17 @@ public: void removeFromCache(not_null user); [[nodiscard]] TimeId dateForUser(not_null user); + std::unique_ptr saveState() const override; + void restoreState(std::unique_ptr state) override; + private: + struct SavedState : SavedStateBase { + QString query; + TimeId offsetDate = 0; + UserData *offsetUser = nullptr; + bool allLoaded = false; + bool wasLoading = false; + }; struct Item { not_null user; TimeId date = 0; diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index 7470b1d09..25ad70b2a 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -296,6 +296,7 @@ void Controller::updateSearchControllers( : Section::MediaType::kCount; const auto hasMediaSearch = isMedia && SharedMediaAllowSearch(mediaType); + const auto hasRequestsListSearch = (type == Type::RequestsList); const auto hasCommonGroupsSearch = (type == Type::CommonGroups); const auto hasDownloadsSearch = (type == Type::Downloads); const auto hasMembersSearch = (type == Type::Members) @@ -312,6 +313,7 @@ void Controller::updateSearchControllers( _searchController = nullptr; } if (hasMediaSearch + || hasRequestsListSearch || hasCommonGroupsSearch || hasDownloadsSearch || hasMembersSearch) { diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index f321e6956..6d7d2e92b 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -106,6 +106,7 @@ public: Media, CommonGroups, SimilarChannels, + RequestsList, SavedSublists, PeerGifts, Members, diff --git a/Telegram/SourceFiles/info/info_memento.cpp b/Telegram/SourceFiles/info/info_memento.cpp index a8018f21c..e26bab419 100644 --- a/Telegram/SourceFiles/info/info_memento.cpp +++ b/Telegram/SourceFiles/info/info_memento.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/saved/info_saved_sublists_widget.h" #include "info/settings/info_settings_widget.h" #include "info/similar_channels/info_similar_channels_widget.h" +#include "info/requests_list/info_requests_list_widget.h" #include "info/peer_gifts/info_peer_gifts_widget.h" #include "info/polls/info_polls_results_widget.h" #include "info/info_section_widget.h" @@ -149,6 +150,8 @@ std::shared_ptr Memento::DefaultContent( case Section::Type::SimilarChannels: return std::make_shared( peer->asChannel()); + case Section::Type::RequestsList: + return std::make_shared(peer->asChannel()); case Section::Type::PeerGifts: return std::make_shared(peer->asUser()); case Section::Type::SavedSublists: diff --git a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp new file mode 100644 index 000000000..ba68ea345 --- /dev/null +++ b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp @@ -0,0 +1,287 @@ +/* +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 "info/requests_list/info_requests_list_widget.h" + +#include "api/api_chat_participants.h" +#include "apiwrap.h" +#include "boxes/peers/edit_peer_requests_box.h" +#include "boxes/peer_list_box.h" +#include "data/data_channel.h" +#include "data/data_peer_values.h" +#include "data/data_session.h" +#include "info/info_controller.h" +#include "main/main_session.h" +#include "ui/widgets/scroll_area.h" +#include "ui/search_field_controller.h" +#include "ui/ui_utility.h" +#include "lang/lang_keys.h" +#include "styles/style_info.h" +#include "styles/style_widgets.h" +#include "boxes/peers/edit_peer_requests_box.h" + +namespace Info::RequestsList { +namespace { + +} // namespace + +class InnerWidget final + : public Ui::RpWidget + , private PeerListContentDelegate { +public: + InnerWidget( + QWidget *parent, + not_null controller, + not_null channel); + + [[nodiscard]] not_null channel() const { + return _channel; + } + + rpl::producer scrollToRequests() const; + + int desiredHeight() const; + + void saveState(not_null memento); + void restoreState(not_null memento); + +protected: + void visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) override; + +private: + using ListWidget = PeerListContent; + + // PeerListContentDelegate interface + void peerListSetTitle(rpl::producer title) override; + void peerListSetAdditionalTitle(rpl::producer title) override; + bool peerListIsRowChecked(not_null row) override; + int peerListSelectedRowsCount() override; + void peerListScrollToTop() override; + void peerListAddSelectedPeerInBunch(not_null peer) override; + void peerListAddSelectedRowInBunch(not_null row) override; + void peerListFinishSelectedRowsBunch() override; + void peerListSetDescription(object_ptr description) override; + std::shared_ptr peerListUiShow() override; + + object_ptr setupList( + RpWidget *parent, + not_null controller); + + const std::shared_ptr _show; + not_null _controller; + const not_null _channel; + std::unique_ptr _listController; + object_ptr _list; + + rpl::event_stream _scrollToRequests; +}; + +InnerWidget::InnerWidget( + QWidget *parent, + not_null controller, + not_null channel) +: RpWidget(parent) +, _show(controller->uiShow()) +, _controller(controller) +, _channel(channel) +, _listController(std::make_unique( + controller, + _channel)) +, _list(setupList(this, _listController.get())) { + setContent(_list.data()); + _listController->setDelegate(static_cast(this)); + + controller->searchFieldController()->queryValue( + ) | rpl::start_with_next([this](QString &&query) { + peerListScrollToTop(); + content()->searchQueryChanged(std::move(query)); + }, lifetime()); +} + +void InnerWidget::visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) { + setChildVisibleTopBottom(_list, visibleTop, visibleBottom); +} + +void InnerWidget::saveState(not_null memento) { + memento->setListState(_listController->saveState()); +} + +void InnerWidget::restoreState(not_null memento) { + _listController->restoreState(memento->listState()); +} + +rpl::producer InnerWidget::scrollToRequests() const { + return _scrollToRequests.events(); +} + +int InnerWidget::desiredHeight() const { + auto desired = 0; + desired += _list->fullRowsCount() * st::infoMembersList.item.height; + return qMax(height(), desired); +} + +object_ptr InnerWidget::setupList( + RpWidget *parent, + not_null controller) { + auto result = object_ptr(parent, controller); + result->scrollToRequests( + ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { + auto addmin = (request.ymin < 0) ? 0 : st::infoCommonGroupsMargin.top(); + auto addmax = (request.ymax < 0) ? 0 : st::infoCommonGroupsMargin.top(); + _scrollToRequests.fire({ + request.ymin + addmin, + request.ymax + addmax }); + }, result->lifetime()); + result->moveToLeft(0, st::infoCommonGroupsMargin.top()); + + parent->widthValue( + ) | rpl::start_with_next([list = result.data()](int newWidth) { + list->resizeToWidth(newWidth); + }, result->lifetime()); + + result->heightValue( + ) | rpl::start_with_next([parent](int listHeight) { + auto newHeight = st::infoCommonGroupsMargin.top() + + listHeight + + st::infoCommonGroupsMargin.bottom(); + parent->resize(parent->width(), newHeight); + }, result->lifetime()); + + return result; +} + +void InnerWidget::peerListSetTitle(rpl::producer title) { +} + +void InnerWidget::peerListSetAdditionalTitle(rpl::producer title) { +} + +bool InnerWidget::peerListIsRowChecked(not_null row) { + return false; +} + +int InnerWidget::peerListSelectedRowsCount() { + return 0; +} + +void InnerWidget::peerListScrollToTop() { + _scrollToRequests.fire({ -1, -1 }); +} + +void InnerWidget::peerListAddSelectedPeerInBunch(not_null peer) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void InnerWidget::peerListAddSelectedRowInBunch(not_null row) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void InnerWidget::peerListFinishSelectedRowsBunch() { +} + +void InnerWidget::peerListSetDescription( + object_ptr description) { + description.destroy(); +} + +std::shared_ptr InnerWidget::peerListUiShow() { + return _show; +} + +Memento::Memento(not_null channel) +: ContentMemento(channel, nullptr, PeerId()) { +} + +Section Memento::section() const { + return Section(Section::Type::RequestsList); +} + +not_null Memento::channel() const { + return peer()->asChannel(); +} + +object_ptr Memento::createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) { + auto result = object_ptr(parent, controller, channel()); + result->setInternalState(geometry, this); + return result; +} + +void Memento::setListState(std::unique_ptr state) { + _listState = std::move(state); +} + +std::unique_ptr Memento::listState() { + return std::move(_listState); +} + +Memento::~Memento() = default; + +Widget::Widget( + QWidget *parent, + not_null controller, + not_null channel) +: ContentWidget(parent, controller) { + controller->setSearchEnabledByContent(true); + _inner = setInnerWidget(object_ptr( + this, + controller, + channel)); +} + +rpl::producer Widget::title() { + return tr::lng_manage_peer_requests(); +} + +not_null Widget::channel() const { + return _inner->channel(); +} + +bool Widget::showInternal(not_null memento) { + if (!controller()->validateMementoPeer(memento)) { + return false; + } + if (auto requestsMemento = dynamic_cast(memento.get())) { + if (requestsMemento->channel() == channel()) { + restoreState(requestsMemento); + return true; + } + } + return false; +} + +void Widget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +std::shared_ptr Widget::doCreateMemento() { + auto result = std::make_shared(channel()); + saveState(result.get()); + return result; +} + +void Widget::saveState(not_null memento) { + memento->setScrollTop(scrollTopSave()); + _inner->saveState(memento); +} + +void Widget::restoreState(not_null memento) { + _inner->restoreState(memento); + scrollTopRestore(memento->scrollTop()); +} + +} // namespace Info::RequestsList diff --git a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h new file mode 100644 index 000000000..d61fc938e --- /dev/null +++ b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h @@ -0,0 +1,68 @@ +/* +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 "info/info_content_widget.h" + +class ChannelData; +struct PeerListState; + +namespace Info::RequestsList { + +class InnerWidget; + +class Memento final : public ContentMemento { +public: + explicit Memento(not_null channel); + + object_ptr createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) override; + + Section section() const override; + + [[nodiscard]] not_null channel() const; + + void setListState(std::unique_ptr state); + std::unique_ptr listState(); + + ~Memento(); + +private: + std::unique_ptr _listState; +}; + +class Widget final : public ContentWidget { +public: + Widget( + QWidget *parent, + not_null controller, + not_null channel); + + [[nodiscard]] not_null channel() const; + + bool showInternal( + not_null memento) override; + + void setInternalState( + const QRect &geometry, + not_null memento); + + rpl::producer title() override; + +private: + void saveState(not_null memento); + void restoreState(not_null memento); + + std::shared_ptr doCreateMemento() override; + + InnerWidget *_inner = nullptr; +}; + +} // namespace Info::RequestsList