/* 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_rtmp.h" #include "apiwrap.h" #include "calls/group/calls_group_common.h" #include "data/data_peer.h" #include "lang/lang_keys.h" #include "main/main_account.h" #include "main/main_session.h" #include "settings/settings_common.h" // AddDivider. #include "ui/boxes/confirm_box.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/vertical_layout.h" #include "styles/style_boxes.h" #include "styles/style_calls.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include #include namespace Calls::Group { namespace { constexpr auto kPasswordCharAmount = 24; void StartWithBox( not_null box, Fn done, Fn revoke, Fn)> showBox, Fn showToast, rpl::producer &&data) { struct State { base::unique_qptr menu; }; const auto state = box->lifetime().make_state(); StartRtmpProcess::FillRtmpRows( box->verticalLayout(), true, std::move(showBox), std::move(showToast), std::move(data), &st::boxLabel, &st::groupCallRtmpShowButton, &st::settingsSubsectionTitle, &st::attentionBoxButton, &st::defaultPopupMenu); box->setTitle(tr::lng_group_call_rtmp_title()); Settings::AddDividerText( box->verticalLayout(), tr::lng_group_call_rtmp_info()); box->addButton(tr::lng_group_call_rtmp_start(), done); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->setWidth(st::boxWideWidth); { const auto top = box->addTopButton(st::infoTopBarMenu); top->setClickedCallback([=] { state->menu = base::make_unique_q( top, st::popupMenuWithIcons); state->menu->addAction( tr::lng_group_invite_context_revoke(tr::now), revoke, &st::menuIconRemove); state->menu->setForcedOrigin( Ui::PanelAnimation::Origin::TopRight); top->setForceRippled(true); const auto raw = state->menu.get(); raw->setDestroyedCallback([=] { if ((state->menu == raw) && top) { top->setForceRippled(false); } }); state->menu->popup(top->mapToGlobal(top->rect().center())); return true; }); } } } // namespace StartRtmpProcess::~StartRtmpProcess() { if (_request) { _request->peer->session().api().request(_request->id).cancel(); } } void StartRtmpProcess::start( not_null peer, Fn)> showBox, Fn showToast, Fn done) { Expects(done != nullptr); const auto session = &peer->session(); if (_request) { if (_request->peer == peer) { _request->showBox = std::move(showBox); _request->showToast = std::move(showToast); _request->done = std::move(done); return; } session->api().request(_request->id).cancel(); _request = nullptr; } _request = std::make_unique( RtmpRequest{ .peer = peer, .showBox = std::move(showBox), .showToast = std::move(showToast), .done = std::move(done) }); session->account().sessionChanges( ) | rpl::start_with_next([=] { _request = nullptr; }, _request->lifetime); requestUrl(false); } void StartRtmpProcess::requestUrl(bool revoke) { const auto session = &_request->peer->session(); _request->id = session->api().request(MTPphone_GetGroupCallStreamRtmpUrl( _request->peer->input, MTP_bool(revoke) )).done([=](const MTPphone_GroupCallStreamRtmpUrl &result) { auto data = result.match([&]( const MTPDphone_groupCallStreamRtmpUrl &data) { return RtmpInfo{ .url = qs(data.vurl()), .key = qs(data.vkey()) }; }); processUrl(std::move(data)); }).fail([=] { _request->showToast(Lang::Hard::ServerError()); }).send(); } void StartRtmpProcess::processUrl(RtmpInfo data) { if (!_request->box) { createBox(); } _request->data = std::move(data); } void StartRtmpProcess::finish(JoinInfo info) { const auto done = std::move(_request->done); const auto box = _request->box; const auto current = _request->data.current(); _request = nullptr; info.rtmpInfo = current; done(std::move(info)); if (const auto strong = box.data()) { strong->closeBox(); } } void StartRtmpProcess::createBox() { auto done = [=] { const auto peer = _request->peer; finish({ .peer = peer, .joinAs = peer, .rtmp = true }); }; auto revoke = [=] { const auto guard = base::make_weak(&_request->guard); _request->showBox(Ui::MakeConfirmBox({ .text = tr::lng_group_call_rtmp_revoke_sure(), .confirmed = crl::guard(guard, [=](Fn &&close) { requestUrl(true); close(); }), .confirmText = tr::lng_group_invite_context_revoke(), })); }; auto object = Box( StartWithBox, std::move(done), std::move(revoke), _request->showBox, _request->showToast, _request->data.value()); object->boxClosing( ) | rpl::start_with_next([=] { _request = nullptr; }, _request->lifetime); _request->box = Ui::MakeWeak(object.data()); _request->showBox(std::move(object)); } void StartRtmpProcess::FillRtmpRows( not_null container, bool divider, Fn)> showBox, Fn showToast, rpl::producer &&data, const style::FlatLabel *labelStyle, const style::IconButton *showButtonStyle, const style::FlatLabel *subsectionTitleStyle, const style::RoundButton *attentionButtonStyle, const style::PopupMenu *popupMenuStyle) { struct State { rpl::variable hidden = true; rpl::variable key; rpl::variable url; bool warned = false; }; const auto &rowPadding = st::boxRowPadding; const auto passChar = QChar(container->style()->styleHint( QStyle::SH_LineEdit_PasswordCharacter)); const auto state = container->lifetime().make_state(); state->key = rpl::duplicate( data ) | rpl::map([=](const auto &d) { return d.key; }); state->url = std::move( data ) | rpl::map([=](const auto &d) { return d.url; }); const auto addButton = [&]( bool key, rpl::producer &&text) { auto wrap = object_ptr(container); auto button = Ui::CreateChild( wrap.data(), rpl::duplicate(text), st::groupCallRtmpCopyButton); button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); button->setClickedCallback(key ? Fn([=] { QGuiApplication::clipboard()->setText(state->key.current()); showToast(tr::lng_group_call_rtmp_key_copied(tr::now)); }) : Fn([=] { QGuiApplication::clipboard()->setText(state->url.current()); showToast(tr::lng_group_call_rtmp_url_copied(tr::now)); })); Settings::AddSkip(container, st::groupCallRtmpCopyButtonTopSkip); const auto weak = container->add(std::move(wrap), rowPadding); Settings::AddSkip(container, st::groupCallRtmpCopyButtonBottomSkip); button->heightValue( ) | rpl::start_with_next([=](int height) { weak->resize(weak->width(), height); }, container->lifetime()); return weak; }; const auto addLabel = [&](rpl::producer &&text) { const auto label = container->add( object_ptr( container, std::move(text), *labelStyle, *popupMenuStyle), st::boxRowPadding + QMargins(0, 0, showButtonStyle->width, 0)); label->setSelectable(true); label->setBreakEverywhere(true); return label; }; // Server URL. Settings::AddSubsectionTitle( container, tr::lng_group_call_rtmp_url_subtitle(), st::groupCallRtmpSubsectionTitleAddPadding, subsectionTitleStyle); auto urlLabelContent = state->url.value(); addLabel(std::move(urlLabelContent)); Settings::AddSkip(container, st::groupCallRtmpUrlSkip); addButton(false, tr::lng_group_call_rtmp_url_copy()); // if (divider) { Settings::AddDivider(container); } // Stream Key. Settings::AddSkip(container, st::groupCallRtmpKeySubsectionTitleSkip); Settings::AddSubsectionTitle( container, tr::lng_group_call_rtmp_key_subtitle(), st::groupCallRtmpSubsectionTitleAddPadding, subsectionTitleStyle); auto keyLabelContent = rpl::combine( state->hidden.value(), state->key.value() ) | rpl::map([passChar](bool hidden, const QString &key) { return key.isEmpty() ? QString() : hidden ? QString().fill(passChar, kPasswordCharAmount) : key; }) | rpl::after_next([=] { container->resizeToWidth(container->widthNoMargins()); }); const auto streamKeyLabel = addLabel(std::move(keyLabelContent)); streamKeyLabel->setSelectable(false); const auto streamKeyButton = Ui::CreateChild( container.get(), *showButtonStyle); streamKeyLabel->topValue( ) | rpl::start_with_next([=, right = rowPadding.right()](int top) { streamKeyButton->moveToRight( st::groupCallRtmpShowButtonPosition.x(), top + st::groupCallRtmpShowButtonPosition.y()); streamKeyButton->raise(); }, container->lifetime()); streamKeyButton->addClickHandler([=] { const auto toggle = [=] { const auto newValue = !state->hidden.current(); state->hidden = newValue; streamKeyLabel->setSelectable(!newValue); streamKeyLabel->setAttribute( Qt::WA_TransparentForMouseEvents, newValue); }; if (!state->warned && state->hidden.current()) { showBox(Ui::MakeConfirmBox({ .text = tr::lng_group_call_rtmp_key_warning( Ui::Text::RichLangValue), .confirmed = [=](Fn &&close) { state->warned = true; toggle(); close(); }, .confirmText = tr::lng_from_request_understand(), .cancelText = tr::lng_close(), .confirmStyle = attentionButtonStyle, .labelStyle = labelStyle, })); } else { toggle(); } }); addButton(true, tr::lng_group_call_rtmp_key_copy()); // } } // namespace Calls::Group