/* 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/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/layers/generic_box.h" #include "ui/text/text_utilities.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_layers.h" #include "styles/style_media_view.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, Fn join) { box->setTitle(tr::lng_confcall_join_title()); box->addRow( object_ptr( box, tr::lng_confcall_join_text(), st::boxLabel)); box->addButton(tr::lng_confcall_join_button(), [=] { const auto weak = Ui::MakeWeak(box); join(); if (const auto strong = weak.data()) { strong->closeBox(); } }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle() { return { .box = &st::groupCallLinkBox, .close = &st::storiesStealthBoxClose, .centerLabel = &st::groupCallLinkCenteredText, .linkPreview = &st::groupCallLinkPreview, .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) { box->setStyle(st.box ? *st.box : initial ? st::confcallLinkBoxInitial : st::confcallLinkBox); box->setWidth(st::boxWideWidth); box->setNoContentMargin(true); box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] { box->closeBox(); }); 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 invite = std::move(args.invite); 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()) { Core::App().calls().startOrJoinConferenceCall({ .call = call, .linkSlug = std::move(slug), .invite = invite, .migrating = args.migrating, }); } 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 migrating = args.migrating; const auto invite = std::move(args.invite); 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, .migrating = migrating, .finished = finished, .invite = invite, }); }); }).fail([=](const MTP::Error &error) { show->showToast(error.type()); if (const auto onstack = finished) { onstack(QString()); } }).send(); } } // namespace Calls::Group