/* 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_common.h" #include "apiwrap.h" #include "base/platform/base_platform_info.h" #include "base/random.h" #include "boxes/peers/replace_boost_box.h" // CreateUserpicsWithMoreBadge #include "boxes/share_box.h" #include "calls/calls_instance.h" #include "core/application.h" #include "core/local_url_handlers.h" #include "data/data_group_call.h" #include "data/data_session.h" #include "info/bot/starref/info_bot_starref_common.h" #include "ui/boxes/boost_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/popup_menu.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/painter.h" #include "ui/vertical_list.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "window/window_session_controller.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_media_view.h" #include "styles/style_menu_icons.h" #include "styles/style_calls.h" #include "styles/style_chat.h" #include #include namespace Calls::Group { namespace { [[nodiscard]] QString ExtractConferenceSlug(const QString &link) { const auto local = Core::TryConvertUrlToLocal(link); const auto parts1 = QStringView(local).split('#'); if (!parts1.isEmpty()) { const auto parts2 = parts1.front().split('&'); if (!parts2.isEmpty()) { const auto parts3 = parts2.front().split(u"slug="_q); if (parts3.size() > 1) { return parts3.back().toString(); } } } return QString(); } } // namespace object_ptr ScreenSharingPrivacyRequestBox() { #ifdef Q_OS_MAC if (!Platform::IsMac10_15OrGreater()) { return { nullptr }; } return Box([=](not_null box) { box->addRow( object_ptr( box.get(), rpl::combine( tr::lng_group_call_mac_screencast_access(), tr::lng_group_call_mac_recording() ) | rpl::map([](QString a, QString b) { auto result = Ui::Text::RichLangValue(a); result.append("\n\n").append(Ui::Text::RichLangValue(b)); return result; }), st::groupCallBoxLabel), style::margins( st::boxRowPadding.left(), st::boxPadding.top(), st::boxRowPadding.right(), st::boxPadding.bottom())); box->addButton(tr::lng_group_call_mac_settings(), [=] { Platform::OpenDesktopCapturePrivacySettings(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); }); #else // Q_OS_MAC return { nullptr }; #endif // Q_OS_MAC } void ConferenceCallJoinConfirm( not_null box, std::shared_ptr call, UserData *maybeInviter, Fn close)> join) { box->setStyle(st::confcallJoinBox); box->setWidth(st::boxWideWidth); box->setNoContentMargin(true); box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); const auto logoSize = st::confcallJoinLogo.size(); const auto logoOuter = logoSize.grownBy(st::confcallJoinLogoPadding); const auto logo = box->addRow( object_ptr(box), st::boxRowPadding + st::confcallLinkHeaderIconPadding); logo->resize(logo->width(), logoOuter.height()); logo->paintRequest() | rpl::start_with_next([=] { if (logo->width() < logoOuter.width()) { return; } auto p = QPainter(logo); auto hq = PainterHighQualityEnabler(p); const auto x = (logo->width() - logoOuter.width()) / 2; const auto outer = QRect(QPoint(x, 0), logoOuter); p.setBrush(st::windowBgActive); p.setPen(Qt::NoPen); p.drawEllipse(outer); st::confcallJoinLogo.paintInCenter(p, outer); }, logo->lifetime()); box->addRow( object_ptr>( box, object_ptr( box, tr::lng_confcall_join_title(), st::boxTitle)), st::boxRowPadding + st::confcallLinkTitlePadding); const auto wrapName = [&](not_null peer) { return rpl::single(Ui::Text::Bold(peer->shortName())); }; box->addRow( object_ptr( box, (maybeInviter ? tr::lng_confcall_join_text_inviter( lt_user, wrapName(maybeInviter), Ui::Text::RichLangValue) : tr::lng_confcall_join_text(Ui::Text::RichLangValue)), st::confcallLinkCenteredText), st::boxRowPadding )->setTryMakeSimilarLines(true); const auto &participants = call->participants(); const auto known = int(participants.size()); if (known) { const auto sep = box->addRow( object_ptr(box), st::boxRowPadding + st::confcallJoinSepPadding); sep->resize(sep->width(), st::normalFont->height); sep->paintRequest() | rpl::start_with_next([=] { auto p = QPainter(sep); const auto line = st::lineWidth; const auto top = st::confcallLinkFooterOrLineTop; const auto fg = st::windowSubTextFg->b; p.setOpacity(0.2); p.fillRect(0, top, sep->width(), line, fg); }, sep->lifetime()); auto peers = std::vector>(); for (const auto &participant : participants) { peers.push_back(participant.peer); if (peers.size() == 3) { break; } } box->addRow( CreateUserpicsWithMoreBadge( box, rpl::single(peers), st::confcallJoinUserpics, known), st::boxRowPadding + st::confcallJoinUserpicsPadding); const auto wrapByIndex = [&](int index) { Expects(index >= 0 && index < known); return wrapName(participants[index].peer); }; auto text = (known == 1) ? tr::lng_confcall_already_joined_one( lt_user, wrapByIndex(0), Ui::Text::RichLangValue) : (known == 2) ? tr::lng_confcall_already_joined_two( lt_user, wrapByIndex(0), lt_other, wrapByIndex(1), Ui::Text::RichLangValue) : (known == 3) ? tr::lng_confcall_already_joined_three( lt_user, wrapByIndex(0), lt_other, wrapByIndex(1), lt_third, wrapByIndex(2), Ui::Text::RichLangValue) : tr::lng_confcall_already_joined_many( lt_count, rpl::single(1. * (std::max(known, call->fullCount()) - 2)), lt_user, wrapByIndex(0), lt_other, wrapByIndex(1), Ui::Text::RichLangValue); box->addRow( object_ptr( box, std::move(text), st::confcallLinkCenteredText), st::boxRowPadding )->setTryMakeSimilarLines(true); } const auto joinAndClose = [=] { join([weak = Ui::MakeWeak(box)] { if (const auto strong = weak.data()) { strong->closeBox(); } }); }; Info::BotStarRef::AddFullWidthButton( box, tr::lng_confcall_join_button(), joinAndClose, &st::confcallLinkButton); } ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle() { return { .box = &st::groupCallLinkBox, .menuToggle = &st::groupCallLinkMenu, .menu = &st::groupCallPopupMenuWithIcons, .close = &st::storiesStealthBoxClose, .centerLabel = &st::groupCallLinkCenteredText, .linkPreview = &st::groupCallLinkPreview, .contextRevoke = &st::mediaMenuIconRemove, .shareBox = std::make_shared( DarkShareBoxStyle()), }; } void ShowConferenceCallLinkBox( std::shared_ptr show, std::shared_ptr call, const QString &link, ConferenceCallLinkArgs &&args) { const auto st = args.st; const auto initial = args.initial; show->showBox(Box([=](not_null box) { struct State { base::unique_qptr menu; bool resetting = false; }; const auto state = box->lifetime().make_state(); box->setStyle(st.box ? *st.box : initial ? st::confcallLinkBoxInitial : st::confcallLinkBox); box->setWidth(st::boxWideWidth); box->setNoContentMargin(true); const auto close = box->addTopButton( st.close ? *st.close : st::boxTitleClose, [=] { box->closeBox(); }); if (!args.initial && call->canManage()) { const auto toggle = Ui::CreateChild( close->parentWidget(), st.menuToggle ? *st.menuToggle : st::confcallLinkMenu); const auto handler = [=] { if (state->resetting) { return; } state->resetting = true; using Flag = MTPphone_ToggleGroupCallSettings::Flag; call->session().api().request( MTPphone_ToggleGroupCallSettings( MTP_flags(Flag::f_reset_invite_hash), call->input(), MTPbool()) // join_muted ).done([=] { auto copy = args; const auto weak = Ui::MakeWeak(box); copy.finished = [=](QString link) { if (const auto strong = weak.data()) { strong->closeBox(); } show->showToast({ .title = tr::lng_confcall_link_revoked_title( tr::now), .text = { tr::lng_confcall_link_revoked_text(tr::now), }, }); }; ExportConferenceCallLink( show, call, std::move(copy)); }).send(); }; toggle->setClickedCallback([=] { state->menu = base::make_unique_q( toggle, st.menu ? *st.menu : st::popupMenuWithIcons); state->menu->addAction( tr::lng_confcall_link_revoke(tr::now), handler, (st.contextRevoke ? st.contextRevoke : &st::menuIconRemove)); state->menu->popup(QCursor::pos()); }); close->geometryValue( ) | rpl::start_with_next([=](QRect geometry) { toggle->moveToLeft( geometry.x() - toggle->width(), geometry.y()); }, close->lifetime()); } box->addRow( Info::BotStarRef::CreateLinkHeaderIcon(box, &call->session()), st::boxRowPadding + st::confcallLinkHeaderIconPadding); box->addRow( object_ptr>( box, object_ptr( box, tr::lng_confcall_link_title(), st.box ? st.box->title : st::boxTitle)), st::boxRowPadding + st::confcallLinkTitlePadding); box->addRow( object_ptr( box, tr::lng_confcall_link_about(), (st.centerLabel ? *st.centerLabel : st::confcallLinkCenteredText)), st::boxRowPadding )->setTryMakeSimilarLines(true); Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 2); const auto preview = box->addRow( Info::BotStarRef::MakeLinkLabel( box, link, st.linkPreview)); Ui::AddSkip(box->verticalLayout()); const auto copyCallback = [=] { QApplication::clipboard()->setText(link); show->showToast(tr::lng_username_copied(tr::now)); }; const auto shareCallback = [=] { FastShareLink( show, link, st.shareBox ? *st.shareBox : ShareBoxStyleOverrides()); }; preview->setClickedCallback(copyCallback); [[maybe_unused]] const auto share = box->addButton( tr::lng_group_invite_share(), shareCallback, st::confcallLinkShareButton); [[maybe_unused]] const auto copy = box->addButton( tr::lng_group_invite_copy(), copyCallback, st::confcallLinkCopyButton); rpl::combine( box->widthValue(), copy->widthValue(), share->widthValue() ) | rpl::start_with_next([=] { const auto width = st::boxWideWidth; const auto padding = st::confcallLinkBox.buttonPadding; const auto available = width - 2 * padding.right(); const auto buttonWidth = (available - padding.left()) / 2; copy->resizeToWidth(buttonWidth); share->resizeToWidth(buttonWidth); copy->moveToLeft(padding.right(), copy->y(), width); share->moveToRight(padding.right(), share->y(), width); }, box->lifetime()); if (!initial) { return; } const auto sep = Ui::CreateChild( copy->parentWidget(), tr::lng_confcall_link_or(), st::confcallLinkFooterOr); sep->paintRequest() | rpl::start_with_next([=] { auto p = QPainter(sep); const auto text = sep->textMaxWidth(); const auto white = (sep->width() - 2 * text) / 2; const auto line = st::lineWidth; const auto top = st::confcallLinkFooterOrLineTop; const auto fg = st::windowSubTextFg->b; p.setOpacity(0.4); p.fillRect(0, top, white, line, fg); p.fillRect(sep->width() - white, top, white, line, fg); }, sep->lifetime()); const auto footer = Ui::CreateChild( copy->parentWidget(), tr::lng_confcall_link_join( lt_link, tr::lng_confcall_link_join_link( lt_arrow, rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)), [](QString v) { return Ui::Text::Link(v); }), Ui::Text::WithEntities), (st.centerLabel ? *st.centerLabel : st::confcallLinkCenteredText)); footer->setTryMakeSimilarLines(true); footer->setClickHandlerFilter([=](const auto &...) { if (auto slug = ExtractConferenceSlug(link); !slug.isEmpty()) { Core::App().calls().startOrJoinConferenceCall({ .call = call, .linkSlug = std::move(slug), }); } return false; }); copy->geometryValue() | rpl::start_with_next([=](QRect geometry) { const auto width = st::boxWideWidth - st::boxRowPadding.left() - st::boxRowPadding.right(); footer->resizeToWidth(width); const auto top = geometry.y() + geometry.height() + st::confcallLinkFooterOrTop; sep->resizeToWidth(width / 2); sep->move( st::boxRowPadding.left() + (width - sep->width()) / 2, top); footer->moveToLeft( st::boxRowPadding.left(), top + sep->height() + st::confcallLinkFooterOrSkip); }, footer->lifetime()); })); } void ExportConferenceCallLink( std::shared_ptr show, std::shared_ptr call, ConferenceCallLinkArgs &&args) { const auto session = &show->session(); const auto info = std::move(args.info); const auto finished = std::move(args.finished); using Flag = MTPphone_ExportGroupCallInvite::Flag; session->api().request(MTPphone_ExportGroupCallInvite( MTP_flags(Flag::f_can_self_unmute), call->input() )).done([=](const MTPphone_ExportedGroupCallInvite &result) { const auto link = qs(result.data().vlink()); if (args.joining) { if (auto slug = ExtractConferenceSlug(link); !slug.isEmpty()) { auto copy = info; copy.call = call; copy.linkSlug = std::move(slug); Core::App().calls().startOrJoinConferenceCall( std::move(copy)); } if (const auto onstack = finished) { finished(QString()); } return; } Calls::Group::ShowConferenceCallLinkBox( show, call, link, base::duplicate(args)); if (const auto onstack = finished) { finished(link); } }).fail([=](const MTP::Error &error) { show->showToast(error.type()); if (const auto onstack = finished) { finished(QString()); } }).send(); } void MakeConferenceCall(ConferenceFactoryArgs &&args) { const auto show = std::move(args.show); const auto finished = std::move(args.finished); const auto joining = args.joining; const auto info = std::move(args.info); const auto session = &show->session(); session->api().request(MTPphone_CreateConferenceCall( MTP_int(base::RandomValue()) )).done([=](const MTPphone_GroupCall &result) { result.data().vcall().match([&](const auto &data) { const auto call = session->data().sharedConferenceCall( data.vid().v, data.vaccess_hash().v); call->processFullCall(result); Calls::Group::ExportConferenceCallLink(show, call, { .initial = true, .joining = joining, .finished = finished, .info = info, }); }); }).fail([=](const MTP::Error &error) { show->showToast(error.type()); if (const auto onstack = finished) { onstack(QString()); } }).send(); } } // namespace Calls::Group