/* 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/select_future_owner_box.h" #include "api/api_chat_participants.h" #include "api/api_cloud_password.h" #include "apiwrap.h" #include "base/unixtime.h" #include "boxes/filters/edit_filter_chats_list.h" #include "boxes/passcode_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/peer_lists_box.h" #include "boxes/peers/replace_boost_box.h" // CreateUserpicsTransfer. #include "core/application.h" #include "core/core_cloud_password.h" #include "data/data_channel.h" #include "dialogs/ui/chat_search_empty.h" #include "boxes/peers/channel_ownership_transfer.h" #include "data/data_session.h" #include "data/data_user.h" #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/boxes/confirm_box.h" #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "window/window_controller.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" namespace { enum class ParticipantType { Admins, Members }; class ParticipantsController : public PeerListController { public: ParticipantsController( not_null window, not_null channel, ParticipantType type); Main::Session &session() const override; void prepare() override; void rowClicked(not_null row) override; void loadMoreRows() override; void itemDeselectedHook(not_null peer) override; void setOnRowClicked(Fn callback); rpl::producer<> itemDeselected() const; private: const not_null _window; const not_null _channel; const ParticipantType _type; MTP::Sender _api; Fn _onRowClicked; rpl::event_stream<> _itemDeselected; mtpRequestId _loadRequestId = 0; int _offset = 0; bool _allLoaded = false; }; ParticipantsController::ParticipantsController( not_null window, not_null channel, ParticipantType type) : _window(window) , _channel(channel) , _type(type) , _api(&channel->session().mtp()) { } Main::Session &ParticipantsController::session() const { return _channel->session(); } void ParticipantsController::setOnRowClicked(Fn callback) { _onRowClicked = callback; } void ParticipantsController::prepare() { loadMoreRows(); } void ParticipantsController::rowClicked(not_null row) { delegate()->peerListSetRowChecked( row, !delegate()->peerListIsRowChecked(row)); for (auto i = 0; i < delegate()->peerListFullRowsCount(); ++i) { auto r = delegate()->peerListRowAt(i); if (r != row) { delegate()->peerListSetRowChecked(r, false); } } if (_onRowClicked) { _onRowClicked(); } } void ParticipantsController::itemDeselectedHook(not_null peer) { _itemDeselected.fire({}); } rpl::producer<> ParticipantsController::itemDeselected() const { return _itemDeselected.events(); } void ParticipantsController::loadMoreRows() { if (_loadRequestId || _allLoaded) { return; } const auto perPage = (_offset > 0) ? 200 : 50; const auto participantsHash = uint64(0); const auto filter = (_type == ParticipantType::Admins) ? MTP_channelParticipantsAdmins() : MTP_channelParticipantsRecent(); _loadRequestId = _api.request(MTPchannels_GetParticipants( _channel->inputChannel(), filter, MTP_int(_offset), MTP_int(perPage), MTP_long(participantsHash) )).done([=](const MTPchannels_ChannelParticipants &result) { _loadRequestId = 0; auto added = false; result.match([&](const MTPDchannels_channelParticipants &data) { const auto &[availableCount, list] = Api::ChatParticipants::Parse( _channel, data); for (const auto &participant : list) { if (const auto user = _channel->owner().userLoaded( participant.userId())) { if (delegate()->peerListFindRow(user->id.value)) { continue; } if (user->isBot()) { continue; } using Type = Api::ChatParticipant::Type; if ((participant.type() == Type::Creator) || (_type == ParticipantType::Members && (participant.type() == Type::Admin))) { continue; } auto row = std::make_unique(user); const auto promotedSince = participant.promotedSince(); row->setCustomStatus( (promotedSince ? tr::lng_select_next_owner_box_status_promoted : tr::lng_select_next_owner_box_status_joined)( tr::now, lt_date, Ui::FormatDateTime( base::unixtime::parse(promotedSince ? promotedSince : participant.memberSince())))); delegate()->peerListAppendRow(std::move(row)); added = true; } } if (const auto size = list.size()) { _offset += size; } else { _allLoaded = true; } }, [](const MTPDchannels_channelParticipantsNotModified &) { }); if (!added && _offset > 0) { _allLoaded = true; } delegate()->peerListRefreshRows(); }).fail([=] { _loadRequestId = 0; }).send(); } } // namespace void SelectFutureOwnerbox( not_null box, not_null channel, not_null user) { const auto content = box->verticalLayout(); Ui::AddSkip(content); Ui::AddSkip(content); content->add( CreateUserpicsTransfer( content, rpl::single(std::vector>{ user->session().user(), channel, }), user, UserpicsTransferType::ChannelFutureOwner), st::boxRowPadding); Ui::AddSkip(content); Ui::AddSkip(content); content->add( object_ptr( content, channel->isMegagroup() ? tr::lng_leave_next_owner_box_title_group() : tr::lng_leave_next_owner_box_title(), box->getDelegate()->style().title), st::boxRowPadding); Ui::AddSkip(content); Ui::AddSkip(content); const auto adminsAreEqual = (channel->adminsCount() <= 1); content->add( object_ptr( content, (adminsAreEqual ? tr::lng_leave_next_owner_box_about : tr::lng_leave_next_owner_box_about_admin)( lt_user, Info::Profile::NameValue(user) | rpl::map(tr::marked), lt_chat, Info::Profile::NameValue(channel) | rpl::map(tr::marked), tr::rich), st::boxLabel), st::boxRowPadding); Ui::AddSkip(content); Ui::AddSkip(content); Ui::AddSkip(content); const auto select = content->add( object_ptr( content, !adminsAreEqual ? tr::lng_select_next_owner_box() : tr::lng_select_next_owner_box_admin(), st::defaultLightButton), st::boxRowPadding, style::al_justify); Ui::AddSkip(content); const auto cancel = content->add( object_ptr( content, tr::lng_cancel(), st::defaultLightButton), st::boxRowPadding, style::al_justify); cancel->setClickedCallback([=] { box->closeBox(); }); Ui::AddSkip(content); const auto leave = content->add( object_ptr( content, channel->isMegagroup() ? tr::lng_profile_leave_group() : tr::lng_profile_leave_channel(), st::attentionBoxButton), st::boxRowPadding, style::al_justify); leave->setClickedCallback([=, revoke = false] { channel->session().api().deleteConversation(channel, revoke); box->closeBox(); }); select->setClickedCallback([=] { const auto window = Core::App().findWindow(box); const auto sessionController = window ? window->sessionController() : nullptr; if (!sessionController) { return; } auto adminsOwned = std::make_unique( sessionController, channel, ParticipantType::Admins); auto membersOwned = std::make_unique( sessionController, channel, ParticipantType::Members); const auto admins = adminsOwned.get(); const auto members = membersOwned.get(); auto initBox = [=](not_null selectBox) { struct State { base::unique_qptr noLists; rpl::event_stream<> selectionChanges; }; const auto state = selectBox->lifetime().make_state(); const auto uncheckOtherList = [=]( not_null otherController) { auto delegate = otherController->delegate(); const auto full = delegate->peerListFullRowsCount(); for (auto i = 0; i < full; ++i) { delegate->peerListSetRowChecked( delegate->peerListRowAt(i), false); } state->selectionChanges.fire({}); }; admins->setOnRowClicked([=] { uncheckOtherList(members); }); members->setOnRowClicked([=] { uncheckOtherList(admins); }); selectBox->setTitle(!adminsAreEqual ? tr::lng_select_next_owner_box_title() : tr::lng_select_next_owner_box_title_admin()); const auto searchEnabled = PeerListSearchMode::Enabled; admins->delegate()->peerListSetSearchMode(searchEnabled); members->delegate()->peerListSetSearchMode(searchEnabled); rpl::merge( admins->itemDeselected(), members->itemDeselected() ) | rpl::on_next([=] { state->selectionChanges.fire({}); }, selectBox->lifetime()); const auto separatorAdmins = selectBox->addSeparatorBefore( 0, CreatePeerListSectionSubtitle( selectBox, !channel->isMegagroup() ? tr::lng_select_next_owner_box_sub_admins() : tr::lng_select_next_owner_box_sub_admins_group())); const auto separatorMembers = selectBox->addSeparatorBefore( 1, CreatePeerListSectionSubtitle( selectBox, !channel->isMegagroup() ? tr::lng_select_next_owner_box_sub_members() : tr::lng_select_next_owner_box_sub_members_group())); rpl::combine( separatorAdmins->heightValue(), separatorMembers->heightValue() ) | rpl::map( (rpl::mappers::_1 + rpl::mappers::_2) > 0 ) | rpl::distinct_until_changed() | rpl::on_next([=](bool has) { qDebug() << "has" << has; if (has) { state->noLists = nullptr; return; } using namespace Dialogs; state->noLists = base::make_unique_q( selectBox, SearchEmpty::Icon::NoResults, (adminsAreEqual ? tr::lng_select_next_owner_box_empty_list : tr::lng_select_next_owner_box_empty_list_admin)( tr::rich)); state->noLists->show(); state->noLists->raise(); selectBox->sizeValue( ) | rpl::filter_size() | rpl::on_next([=](QSize s) { state->noLists->setMinimalHeight(s.height() / 3); state->noLists->resizeToWidth(s.width() / 3 * 2); state->noLists->moveToLeft( (s.width() - state->noLists->width()) / 2, (s.height() - state->noLists->height()) / 2); }, state->noLists->lifetime()); crl::on_main(state->noLists.get(), [=] { state->noLists->animate(); }); }, selectBox->lifetime()); { const auto &st = st::futureOwnerBoxSelect; selectBox->setStyle(st); auto button = object_ptr( selectBox, rpl::conditional( state->selectionChanges.events( ) | rpl::map([=] { return !selectBox->collectSelectedRows().empty(); }), tr::lng_select_next_owner_box_confirm(), tr::lng_close()), st::defaultActiveButton); button->setTextTransform( Ui::RoundButton::TextTransform::NoTransform); const auto raw = button.data(); rpl::combine( state->selectionChanges.events() | rpl::map_to(0), selectBox->widthValue() ) | rpl::on_next([=](int, int width) { raw->resizeToWidth(width - st.buttonPadding.left() - st.buttonPadding.right()); }, selectBox->lifetime()); button->setFullRadius(true); button->setClickedCallback([=] { const auto selected = selectBox->collectSelectedRows(); if (selected.empty()) { return selectBox->closeBox(); } if (const auto user = selected.front()->asUser()) { auto &lifetime = selectBox->lifetime(); lifetime.make_state( channel, user, selectBox->uiShow(), [=](std::shared_ptr show) { const auto revoke = false; channel->session().api().deleteConversation( channel, revoke); show->hideLayer(); })->start(); } }); selectBox->addButton(std::move(button)); state->selectionChanges.fire({}); } }; auto controllers = std::vector>(); controllers.reserve(2); controllers.push_back(std::move(adminsOwned)); controllers.push_back(std::move(membersOwned)); box->uiShow()->showBox( Box(std::move(controllers), initBox)); }); for (const auto &b : { select, cancel, leave }) { b->setFullRadius(true); b->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); } box->setStyle(st::futureOwnerBox); }