diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e62b0b7ac..53a8680b7 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -174,12 +174,14 @@ PRIVATE boxes/peers/edit_peer_invite_link.h boxes/peers/edit_peer_invite_links.cpp boxes/peers/edit_peer_invite_links.h - boxes/peers/edit_peer_type_box.cpp - boxes/peers/edit_peer_type_box.h boxes/peers/edit_peer_history_visibility_box.cpp boxes/peers/edit_peer_history_visibility_box.h boxes/peers/edit_peer_permissions_box.cpp boxes/peers/edit_peer_permissions_box.h + boxes/peers/edit_peer_requests_box.cpp + boxes/peers/edit_peer_requests_box.h + boxes/peers/edit_peer_type_box.cpp + boxes/peers/edit_peer_type_box.h boxes/about_box.cpp boxes/about_box.h boxes/about_sponsored_box.cpp diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 110bd9837..aaaeb44ae 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -270,6 +270,7 @@ bool PeerListController::hasComplexSearch() const { void PeerListController::search(const QString &query) { Expects(hasComplexSearch()); + _searchController->searchQuery(query); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index 225cdecd0..caa9d7483 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -925,9 +925,8 @@ void ParticipantsBoxController::Start( }); } }; - Ui::show( - Box(std::move(controller), initBox), - Ui::LayerOption::KeepOther); + navigation->parentController()->show( + Box(std::move(controller), initBox)); } void ParticipantsBoxController::addNewItem() { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 8060ef883..b94b960f0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_permissions_box.h" #include "boxes/peers/edit_peer_invite_links.h" #include "boxes/peers/edit_linked_chat_box.h" +#include "boxes/peers/edit_peer_requests_box.h" #include "boxes/stickers_box.h" #include "ui/boxes/single_choice_box.h" #include "chat_helpers/emoji_suggestions_widget.h" @@ -1107,22 +1108,23 @@ void Controller::fillPendingRequestsButton() { _controls.buttonsLayout, object_ptr( _controls.buttonsLayout))); + const auto showPendingRequestsBox = [=] { + 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)); + }; AddButtonWithCount( wrap->entity(), (_isGroup ? tr::lng_manage_peer_requests() : tr::lng_manage_peer_requests_channel()), rpl::duplicate(pendingRequestsCount) | ToPositiveNumberString(), - [=] { - _navigation->parentController()->show( - Box( // #TODO requests - ManageInviteLinksBox, - _peer, - _peer->session().user(), - 0, - 0), - Ui::LayerOption::KeepOther); - }, + showPendingRequestsBox, st::infoIconRequests); std::move( pendingRequestsCount diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp new file mode 100644 index 000000000..54df5c758 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp @@ -0,0 +1,344 @@ +/* +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 "boxes/peers/edit_peer_requests_box.h" + +#include "boxes/peer_list_controllers.h" +#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration +#include "data/data_peer.h" +#include "data/data_user.h" +#include "data/data_chat.h" +#include "data/data_channel.h" +#include "data/data_session.h" +#include "main/main_session.h" +#include "mtproto/sender.h" +#include "lang/lang_keys.h" +#include "window/window_session_controller.h" + +namespace { + +constexpr auto kFirstPageCount = 16; +constexpr auto kPerPage = 200; +constexpr auto kServerSearchDelay = crl::time(1000); + +class Row final : public PeerListRow { +public: + Row(not_null user, TimeId date); + + //QSize actionSize() const override; + //void paintAction( + // Painter &p, + // int x, + // int y, + // int outerWidth, + // bool selected, + // bool actionSelected) override; + + //not_null user() const; + +private: + TimeId _date = 0; + +}; + +Row::Row(not_null user, TimeId date) +: PeerListRow(user) +, _date(date) { + setCustomStatus(QString::number(_date)); +} + +} // namespace + +RequestsBoxController::RequestsBoxController( + not_null navigation, + not_null peer) +: PeerListController(CreateSearchController(peer)) +, _navigation(navigation) +, _peer(peer) +, _api(&_peer->session().mtp()) { + subscribeToMigration(); +} + +Main::Session &RequestsBoxController::session() const { + return _peer->session(); +} + +auto RequestsBoxController::CreateSearchController(not_null peer) +-> std::unique_ptr { + return std::make_unique(peer); +} + +std::unique_ptr RequestsBoxController::createSearchRow( + not_null peer) { + if (const auto user = peer->asUser()) { + return createRow(user); + } + return nullptr; +} + +void RequestsBoxController::prepare() { + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + delegate()->peerListSetTitle(_peer->isBroadcast() + ? tr::lng_manage_peer_requests_channel() + : tr::lng_manage_peer_requests()); + setDescriptionText(tr::lng_contacts_loading(tr::now)); + setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now)); + loadMoreRows(); +} + +void RequestsBoxController::loadMoreRows() { + if (searchController() && searchController()->loadMoreRows()) { + return; + } else if (_loadRequestId || _allLoaded) { + return; + } + + // First query is small and fast, next loads a lot of rows. + const auto limit = _offsetDate ? kPerPage : kFirstPageCount; + using Flag = MTPmessages_GetChatInviteImporters::Flag; + _loadRequestId = _api.request(MTPmessages_GetChatInviteImporters( + MTP_flags(Flag::f_requested), + _peer->input, + MTPstring(), // link + MTPstring(), // q + MTP_int(_offsetDate), + _offsetUser ? _offsetUser->inputUser : MTP_inputUserEmpty(), + MTP_int(limit) + )).done([=](const MTPmessages_ChatInviteImporters &result) { + const auto firstLoad = !_offsetDate; + _loadRequestId = 0; + + result.match([&](const MTPDmessages_chatInviteImporters &data) { + const auto count = data.vcount().v; + const auto &importers = data.vimporters().v; + auto &owner = _peer->owner(); + for (const auto &importer : importers) { + importer.match([&](const MTPDchatInviteImporter &data) { + _offsetDate = data.vdate().v; + _offsetUser = owner.user(data.vuser_id()); + appendRow(_offsetUser, _offsetDate); + }); + } + // To be sure - wait for a whole empty result list. + _allLoaded = importers.isEmpty(); + }); + + if (_allLoaded + || (firstLoad && delegate()->peerListFullRowsCount() > 0)) { + refreshDescription(); + } + delegate()->peerListRefreshRows(); + }).fail([=](const MTP::Error &error) { + _loadRequestId = 0; + _allLoaded = true; + }).send(); +} + +void RequestsBoxController::refreshDescription() { + setDescriptionText((delegate()->peerListFullRowsCount() > 0) + ? QString() + : _peer->isBroadcast() + ? tr::lng_group_removed_list_about(tr::now) + : tr::lng_channel_removed_list_about(tr::now)); +} + +void RequestsBoxController::rowClicked(not_null row) { + _navigation->showPeerInfo(row->peer()); +} + +void RequestsBoxController::appendRow( + not_null user, + TimeId date) { + if (!delegate()->peerListFindRow(user->id.value)) { + if (auto row = createRow(user, date)) { + delegate()->peerListAppendRow(std::move(row)); + setDescriptionText(QString()); + } + } +} + +std::unique_ptr RequestsBoxController::createRow( + not_null user, + TimeId date) { + if (!date) { + const auto search = static_cast( + searchController()); + date = search->dateForUser(user); + } + return std::make_unique(user, date); +} + +void RequestsBoxController::subscribeToMigration() { + const auto chat = _peer->asChat(); + if (!chat) { + return; + } + SubscribeToMigration( + chat, + lifetime(), + [=](not_null channel) { migrate(chat, channel); }); +} + +void RequestsBoxController::migrate( + not_null chat, + not_null channel) { + _peer = channel; +} + +RequestsBoxSearchController::RequestsBoxSearchController( + not_null peer) +: _peer(peer) +, _api(&_peer->session().mtp()) { + _timer.setCallback([=] { searchOnServer(); }); +} + +void RequestsBoxSearchController::searchQuery(const QString &query) { + if (_query != query) { + _query = query; + _offsetDate = 0; + _offsetUser = nullptr; + _requestId = 0; + _allLoaded = false; + if (!_query.isEmpty() && !searchInCache()) { + _timer.callOnce(kServerSearchDelay); + } else { + _timer.cancel(); + } + } +} + +void RequestsBoxSearchController::searchOnServer() { + Expects(!_query.isEmpty()); + + loadMoreRows(); +} + +bool RequestsBoxSearchController::isLoading() { + return _timer.isActive() || _requestId; +} + +void RequestsBoxSearchController::removeFromCache(not_null user) { + for (auto &entry : _cache) { + auto &items = entry.second.items; + const auto j = ranges::remove(items, user, &Item::user); + if (j != end(items)) { + entry.second.requestedCount -= (end(items) - j); + items.erase(j, end(items)); + } + } +} + +TimeId RequestsBoxSearchController::dateForUser(not_null user) { + if (const auto i = _dates.find(user); i != end(_dates)) { + return i->second; + } + return {}; +} + +bool RequestsBoxSearchController::searchInCache() { + const auto i = _cache.find(_query); + if (i != _cache.cend()) { + _requestId = 0; + searchDone( + _requestId, + i->second.items, + i->second.requestedCount); + return true; + } + return false; +} + +bool RequestsBoxSearchController::loadMoreRows() { + if (_query.isEmpty()) { + return false; + } else if (_allLoaded || isLoading()) { + return true; + } + // For search we request a lot of rows from the first query. + // (because we've waited for search request by timer already, + // so we don't expect it to be fast, but we want to fill cache). + const auto limit = kPerPage; + using Flag = MTPmessages_GetChatInviteImporters::Flag; + _requestId = _api.request(MTPmessages_GetChatInviteImporters( + MTP_flags(Flag::f_requested | Flag::f_q), + _peer->input, + MTPstring(), // link + MTP_string(_query), + MTP_int(_offsetDate), + _offsetUser ? _offsetUser->inputUser : MTP_inputUserEmpty(), + MTP_int(limit) + )).done([=]( + const MTPmessages_ChatInviteImporters &result, + mtpRequestId requestId) { + auto items = std::vector(); + result.match([&](const MTPDmessages_chatInviteImporters &data) { + const auto count = data.vcount().v; + const auto &importers = data.vimporters().v; + auto &owner = _peer->owner(); + items.reserve(importers.size()); + for (const auto &importer : importers) { + importer.match([&](const MTPDchatInviteImporter &data) { + items.push_back({ + owner.user(data.vuser_id()), + data.vdate().v, + }); + }); + } + }); + searchDone(requestId, items, limit); + + auto it = _queries.find(requestId); + if (it != _queries.cend()) { + const auto &query = it->second.text; + if (it->second.offsetDate == 0) { + auto &entry = _cache[query]; + entry.items = std::move(items); + entry.requestedCount = limit; + } + _queries.erase(it); + } + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + _allLoaded = true; + delegate()->peerListSearchRefreshRows(); + } + }).send(); + + auto entry = Query(); + entry.text = _query; + entry.offsetDate = _offsetDate; + _queries.emplace(_requestId, entry); + return true; +} + +void RequestsBoxSearchController::searchDone( + mtpRequestId requestId, + const std::vector &items, + int requestedCount) { + if (_requestId != requestId) { + return; + } + + _requestId = 0; + if (!_offsetDate) { + _dates.clear(); + } + for (const auto &[user, date] : items) { + _offsetDate = date; + _offsetUser = user; + _dates.emplace(user, date); + delegate()->peerListSearchAddRow(user); + } + if (items.size() < requestedCount) { + // We want cache to have full information about a query with + // small results count (that we don't need the second request). + // So we don't wait for empty list unlike the non-search case. + _allLoaded = true; + } + delegate()->peerListSearchRefreshRows(); +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h new file mode 100644 index 000000000..3c02d22da --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h @@ -0,0 +1,108 @@ +/* +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 "boxes/peer_list_box.h" + +namespace Window { +class SessionNavigation; +} // namespace Window + +class RequestsBoxController final : public PeerListController { +public: + RequestsBoxController( + not_null navigation, + not_null peer); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + void loadMoreRows() override; + + std::unique_ptr createSearchRow( + not_null peer) override; + + bool searchInLocal() override { + AssertIsDebug(); + return false; + } + +private: + static std::unique_ptr CreateSearchController( + not_null peer); + + [[nodiscard]] std::unique_ptr createRow( + not_null user, + TimeId date = 0); + + void appendRow(not_null user, TimeId date); + void refreshDescription(); + void processRequest(not_null user, bool approved); + + void subscribeToMigration(); + void migrate(not_null chat, not_null channel); + + const not_null _navigation; + not_null _peer; + MTP::Sender _api; + + TimeId _offsetDate = 0; + UserData *_offsetUser = nullptr; + mtpRequestId _loadRequestId = 0; + bool _allLoaded = false; + +}; + +// Members, banned and restricted users server side search. +class RequestsBoxSearchController final : public PeerListSearchController { +public: + RequestsBoxSearchController(not_null peer); + + void searchQuery(const QString &query) override; + bool isLoading() override; + bool loadMoreRows() override; + + void removeFromCache(not_null user); + [[nodiscard]] TimeId dateForUser(not_null user); + +private: + struct Item { + not_null user; + TimeId date = 0; + }; + struct CacheEntry { + std::vector items; + int requestedCount = 0; + }; + struct Query { + QString text; + TimeId offsetDate = 0; + UserData *offsetUser = nullptr; + }; + + void searchOnServer(); + bool searchInCache(); + void searchDone( + mtpRequestId requestId, + const std::vector &items, + int requestedCount); + + not_null _peer; + MTP::Sender _api; + + base::Timer _timer; + QString _query; + mtpRequestId _requestId = 0; + TimeId _offsetDate = 0; + UserData *_offsetUser = nullptr; + bool _allLoaded = false; + base::flat_map _cache; + base::flat_map _queries; + base::flat_map, TimeId> _dates; + +};