/* 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 "calls/group/calls_group_invite_controller.h" #include "api/api_chat_participants.h" #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_menu.h" #include "boxes/peer_lists_box.h" #include "data/data_user.h" #include "data/data_channel.h" #include "data/data_session.h" #include "data/data_group_call.h" #include "main/main_session.h" #include "ui/text/text_utilities.h" #include "ui/layers/generic_box.h" #include "ui/widgets/labels.h" #include "apiwrap.h" #include "lang/lang_keys.h" #include "styles/style_calls.h" namespace Calls::Group { namespace { [[nodiscard]] object_ptr CreateSectionSubtitle( QWidget *parent, rpl::producer text) { auto result = object_ptr( parent, st::searchedBarHeight); const auto raw = result.data(); raw->paintRequest( ) | rpl::start_with_next([=](QRect clip) { auto p = QPainter(raw); p.fillRect(clip, st::groupCallMembersBgOver); }, raw->lifetime()); const auto label = Ui::CreateChild( raw, std::move(text), st::groupCallBoxLabel); raw->widthValue( ) | rpl::start_with_next([=](int width) { const auto padding = st::groupCallInviteDividerPadding; const auto available = width - padding.left() - padding.right(); label->resizeToNaturalWidth(available); label->moveToLeft(padding.left(), padding.top(), width); }, label->lifetime()); return result; } } // namespace InviteController::InviteController( not_null peer, base::flat_set> alreadyIn) : ParticipantsBoxController(CreateTag{}, nullptr, peer, Role::Members) , _peer(peer) , _alreadyIn(std::move(alreadyIn)) { SubscribeToMigration( _peer, lifetime(), [=](not_null channel) { _peer = channel; }); } void InviteController::prepare() { delegate()->peerListSetHideEmpty(true); ParticipantsBoxController::prepare(); delegate()->peerListSetAboveWidget(CreateSectionSubtitle( nullptr, tr::lng_group_call_invite_members())); delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle( nullptr, tr::lng_group_call_invite_members())); } void InviteController::rowClicked(not_null row) { delegate()->peerListSetRowChecked(row, !row->checked()); } base::unique_qptr InviteController::rowContextMenu( QWidget *parent, not_null row) { return nullptr; } void InviteController::itemDeselectedHook(not_null peer) { } bool InviteController::hasRowFor(not_null peer) const { return (delegate()->peerListFindRow(peer->id.value) != nullptr); } bool InviteController::isAlreadyIn(not_null user) const { return _alreadyIn.contains(user); } std::unique_ptr InviteController::createRow( not_null participant) const { const auto user = participant->asUser(); if (!user || user->isSelf() || user->isBot() || user->isInaccessible()) { return nullptr; } auto result = std::make_unique(user); _rowAdded.fire_copy(user); _inGroup.emplace(user); if (isAlreadyIn(user)) { result->setDisabledState(PeerListRow::State::DisabledChecked); } return result; } auto InviteController::peersWithRows() const -> not_null>*> { return &_inGroup; } rpl::producer> InviteController::rowAdded() const { return _rowAdded.events(); } InviteContactsController::InviteContactsController( not_null peer, base::flat_set> alreadyIn, not_null>*> inGroup, rpl::producer> discoveredInGroup) : AddParticipantsBoxController(peer, std::move(alreadyIn)) , _inGroup(inGroup) , _discoveredInGroup(std::move(discoveredInGroup)) { } void InviteContactsController::prepareViewHook() { AddParticipantsBoxController::prepareViewHook(); delegate()->peerListSetAboveWidget(CreateSectionSubtitle( nullptr, tr::lng_contacts_header())); delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle( nullptr, tr::lng_group_call_invite_search_results())); std::move( _discoveredInGroup ) | rpl::start_with_next([=](not_null user) { if (auto row = delegate()->peerListFindRow(user->id.value)) { delegate()->peerListRemoveRow(row); } }, _lifetime); } std::unique_ptr InviteContactsController::createRow( not_null user) { return _inGroup->contains(user) ? nullptr : AddParticipantsBoxController::createRow(user); } object_ptr PrepareInviteBox( not_null call, Fn showToast) { const auto real = call->lookupReal(); if (!real) { return nullptr; } const auto peer = call->peer(); auto alreadyIn = peer->owner().invitedToCallUsers(real->id()); for (const auto &participant : real->participants()) { if (const auto user = participant.peer->asUser()) { alreadyIn.emplace(user); } } alreadyIn.emplace(peer->session().user()); auto controller = std::make_unique(peer, alreadyIn); controller->setStyleOverrides( &st::groupCallInviteMembersList, &st::groupCallMultiSelect); auto contactsController = std::make_unique( peer, std::move(alreadyIn), controller->peersWithRows(), controller->rowAdded()); contactsController->setStyleOverrides( &st::groupCallInviteMembersList, &st::groupCallMultiSelect); const auto weak = base::make_weak(call); const auto invite = [=](const std::vector> &users) { const auto call = weak.get(); if (!call) { return; } const auto result = call->inviteUsers(users); if (const auto user = std::get_if>(&result)) { showToast(tr::lng_group_call_invite_done_user( tr::now, lt_user, Ui::Text::Bold((*user)->firstName), Ui::Text::WithEntities)); } else if (const auto count = std::get_if(&result)) { if (*count > 0) { showToast(tr::lng_group_call_invite_done_many( tr::now, lt_count, *count, Ui::Text::RichLangValue)); } } else { Unexpected("Result in GroupCall::inviteUsers."); } }; const auto inviteWithAdd = [=]( const std::vector> &users, const std::vector> &nonMembers, Fn finish) { peer->session().api().chatParticipants().add( peer, nonMembers, true, [=](bool) { invite(users); finish(); }); }; const auto inviteWithConfirmation = [=]( not_null parentBox, const std::vector> &users, const std::vector> &nonMembers, Fn finish) { if (nonMembers.empty()) { invite(users); finish(); return; } const auto name = peer->name(); const auto text = (nonMembers.size() == 1) ? tr::lng_group_call_add_to_group_one( tr::now, lt_user, nonMembers.front()->shortName(), lt_group, name) : (nonMembers.size() < users.size()) ? tr::lng_group_call_add_to_group_some(tr::now, lt_group, name) : tr::lng_group_call_add_to_group_all(tr::now, lt_group, name); const auto shared = std::make_shared>(); const auto finishWithConfirm = [=] { if (*shared) { (*shared)->closeBox(); } finish(); }; const auto done = [=] { inviteWithAdd(users, nonMembers, finishWithConfirm); }; auto box = ConfirmBox({ .text = text, .confirmed = done, .confirmText = tr::lng_participant_invite(), }); *shared = box.data(); parentBox->getDelegate()->showBox( std::move(box), Ui::LayerOption::KeepOther, anim::type::normal); }; auto initBox = [=, controller = controller.get()]( not_null box) { box->setTitle(tr::lng_group_call_invite_title()); box->addButton(tr::lng_group_call_invite_button(), [=] { const auto rows = box->collectSelectedRows(); const auto users = ranges::views::all( rows ) | ranges::views::transform([](not_null peer) { return not_null(peer->asUser()); }) | ranges::to_vector; const auto nonMembers = ranges::views::all( users ) | ranges::views::filter([&](not_null user) { return !controller->hasRowFor(user); }) | ranges::to_vector; const auto finish = [box = Ui::MakeWeak(box)]() { if (box) { box->closeBox(); } }; inviteWithConfirmation(box, users, nonMembers, finish); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); }; auto controllers = std::vector>(); controllers.push_back(std::move(controller)); controllers.push_back(std::move(contactsController)); return Box(std::move(controllers), initBox); } } // namespace Calls::Group