/* 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 "data/data_chat.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 FutureOwnerController : public PeerListController { public: void rowClicked(not_null row) override; void itemDeselectedHook(not_null peer) override; void setOnRowClicked(Fn callback); rpl::producer<> itemDeselected() const; private: Fn _onRowClicked; rpl::event_stream<> _itemDeselected; }; void FutureOwnerController::setOnRowClicked(Fn callback) { _onRowClicked = callback; } void FutureOwnerController::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 FutureOwnerController::itemDeselectedHook(not_null peer) { _itemDeselected.fire({}); } rpl::producer<> FutureOwnerController::itemDeselected() const { return _itemDeselected.events(); } class ParticipantsController : public FutureOwnerController { public: ParticipantsController( not_null channel, ParticipantType type); Main::Session &session() const override; void prepare() override; void loadMoreRows() override; private: const not_null _channel; const ParticipantType _type; MTP::Sender _api; mtpRequestId _loadRequestId = 0; int _offset = 0; bool _allLoaded = false; }; ParticipantsController::ParticipantsController( not_null channel, ParticipantType type) : _channel(channel) , _type(type) , _api(&channel->session().mtp()) { } Main::Session &ParticipantsController::session() const { return _channel->session(); } void ParticipantsController::prepare() { loadMoreRows(); } 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(); } class LegacyParticipantsController : public FutureOwnerController { public: LegacyParticipantsController( not_null chat, ParticipantType type); Main::Session &session() const override; void prepare() override; void loadMoreRows() override; private: const not_null _chat; const ParticipantType _type; }; LegacyParticipantsController::LegacyParticipantsController( not_null chat, ParticipantType type) : _chat(chat) , _type(type) { } Main::Session &LegacyParticipantsController::session() const { return _chat->session(); } void LegacyParticipantsController::prepare() { if (_chat->noParticipantInfo()) { _chat->updateFullForced(); } const auto &source = (_type == ParticipantType::Admins) ? _chat->admins : _chat->participants; for (const auto &user : source) { if (user->isBot()) { continue; } if (user->id == peerFromUser(_chat->creator)) { continue; } if (_type == ParticipantType::Members && _chat->admins.contains(user)) { continue; } delegate()->peerListAppendRow( std::make_unique(user)); } delegate()->peerListRefreshRows(); } void LegacyParticipantsController::loadMoreRows() { } } // namespace void SelectFutureOwnerbox( not_null box, not_null peer, not_null user) { const auto content = box->verticalLayout(); const auto channel = peer->asChannel(); const auto chat = peer->asChat(); const auto isGroup = peer->isMegagroup() || peer->isChat(); const auto isLegacy = (chat != nullptr); Ui::AddSkip(content); Ui::AddSkip(content); content->add( CreateUserpicsTransfer( content, rpl::single(std::vector>{ user->session().user(), peer, }), user, UserpicsTransferType::ChannelFutureOwner), st::boxRowPadding); Ui::AddSkip(content); Ui::AddSkip(content); content->add( object_ptr( content, isGroup ? 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 adminsCount = [&] { if (channel) { return channel->adminsCount(); } else if (chat) { return int(chat->admins.size()) + 1; } return 0; }(); const auto adminsAreEqual = (adminsCount <= 1); content->add( object_ptr( content, (isLegacy ? (adminsAreEqual ? tr::lng_leave_next_owner_box_about_legacy : tr::lng_leave_next_owner_box_about_admin_legacy) : (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(peer) | 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, isGroup ? tr::lng_profile_leave_group() : tr::lng_profile_leave_channel(), st::attentionBoxButton), st::boxRowPadding, style::al_justify); leave->setClickedCallback([=, revoke = false] { peer->session().api().deleteConversation(peer, revoke); box->closeBox(); }); select->setClickedCallback([=] { const auto window = Core::App().findWindow(box); const auto sessionController = window ? window->sessionController() : nullptr; if (!sessionController) { return; } using Pair = std::pair< std::unique_ptr, std::unique_ptr>; auto makeControllers = [&]() -> Pair { if (channel) { return { std::make_unique( channel, ParticipantType::Admins), std::make_unique( channel, ParticipantType::Members), }; } else { return { std::make_unique( chat, ParticipantType::Admins), std::make_unique( chat, ParticipantType::Members), }; } }; auto [adminsOwned, membersOwned] = makeControllers(); 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, !isGroup ? 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, !isGroup ? 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( peer, user, selectBox->uiShow(), [=](std::shared_ptr show) { const auto revoke = false; peer->session().api().deleteConversation( peer, 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); }