diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index ff66fad93..6c7ba8738 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -270,6 +270,9 @@ callMuteMajorBlobMinRadius: 67px; callMuteMajorBlobMaxRadius: 77px; callMuteBlobRadiusForDiameter: 100px; +callMuteToFullScreen: icon {{ "player/player_fullscreen", groupCallIconFg }}; +callMuteFromFullScreen: icon {{ "player/player_minimize", groupCallIconFg }}; + callConnectingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { color: lightButtonFg; thickness: 4px; @@ -489,6 +492,7 @@ callErrorToast: Toast(defaultToast) { groupCallWidth: 380px; groupCallHeight: 580px; +groupCallHeightRtmpMin: 280px; groupCallRipple: RippleAnimation(defaultRippleAnimation) { color: groupCallMembersBgRipple; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 4381935a3..f7f056bb2 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -990,9 +990,10 @@ void GroupCall::playConnectingSoundOnce() { } bool GroupCall::showChooseJoinAs() const { - return (_possibleJoinAs.size() > 1) - || (_possibleJoinAs.size() == 1 - && !_possibleJoinAs.front()->isSelf()); + return !_rtmp + && ((_possibleJoinAs.size() > 1) + || (_possibleJoinAs.size() == 1 + && !_possibleJoinAs.front()->isSelf())); } bool GroupCall::scheduleStartSubscribed() const { @@ -1010,6 +1011,14 @@ bool GroupCall::listenersHidden() const { return _listenersHidden; } +bool GroupCall::emptyRtmp() const { + return _emptyRtmp.current(); +} + +rpl::producer GroupCall::emptyRtmpValue() const { + return _emptyRtmp.value(); +} + Data::GroupCall *GroupCall::lookupReal() const { const auto real = _peer->groupCall(); return (real && real->id() == _id) ? real : nullptr; @@ -2601,7 +2610,8 @@ void GroupCall::requestCurrentTimeStart( ).done([=](const MTPphone_GroupCallStreamChannels &result) { result.match([&](const MTPDphone_groupCallStreamChannels &data) { const auto &list = data.vchannels().v; - if (!list.isEmpty()) { + const auto empty = list.isEmpty(); + if (!empty) { const auto &first = list.front(); first.match([&](const MTPDgroupCallStreamChannel &data) { finish(data.vlast_timestamp_ms().v); @@ -2609,6 +2619,7 @@ void GroupCall::requestCurrentTimeStart( } else { finish(0); } + _emptyRtmp = empty; }); }).fail([=](const MTP::Error &error) { finish(0); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index e7cd9fd7c..b58d74461 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -233,6 +233,8 @@ public: [[nodiscard]] bool scheduleStartSubscribed() const; [[nodiscard]] bool rtmp() const; [[nodiscard]] bool listenersHidden() const; + [[nodiscard]] bool emptyRtmp() const; + [[nodiscard]] rpl::producer emptyRtmpValue() const; [[nodiscard]] Data::GroupCall *lookupReal() const; [[nodiscard]] rpl::producer> real() const; @@ -593,6 +595,7 @@ private: rpl::variable _muted = MuteState::Muted; rpl::variable _canManage = false; rpl::variable _videoIsWorking = false; + rpl::variable _emptyRtmp = false; bool _initialMuteStateSent = false; bool _acceptFields = false; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index bb9488033..5e5490392 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1191,7 +1191,8 @@ base::unique_qptr Members::Controller::createRowContextMenu( const auto muted = (muteState == Row::State::Muted) || (muteState == Row::State::RaisedHand); const auto addCover = true; - const auto addVolumeItem = !muted || isMe(participantPeer); + const auto addVolumeItem = !_call->rtmp() + && (!muted || isMe(participantPeer)); const auto admin = IsGroupCallAdmin(_peer, participantPeer); const auto session = &_peer->session(); const auto getCurrentWindow = [=]() -> Window::SessionController* { @@ -1430,7 +1431,8 @@ void Members::Controller::addMuteActionsToContextMenu( auto mutesFromVolume = rpl::never() | rpl::type_erased(); - const auto addVolumeItem = !muted || isMe(participantPeer); + const auto addVolumeItem = !_call->rtmp() + && (!muted || isMe(participantPeer)); if (addVolumeItem) { auto otherParticipantStateValue = _call->otherParticipantStateValue( @@ -1492,6 +1494,7 @@ void Members::Controller::addMuteActionsToContextMenu( const auto muteAction = [&]() -> QAction* { if (muteState == Row::State::Invited + || _call->rtmp() || isMe(participantPeer) || (muteState == Row::State::Inactive && participantIsCallAdmin diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index d9d7d467b..4234fe97c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -114,6 +114,9 @@ Panel::Panel(not_null call) : _call->scheduleStartSubscribed() ? Ui::CallMuteButtonType::ScheduledNotify : Ui::CallMuteButtonType::ScheduledSilent), + .expandType = ((_call->scheduleDate() || !_call->rtmp()) + ? Ui::CallMuteButtonExpandType::None + : Ui::CallMuteButtonExpandType::Normal), })) , _hangup(widget(), st::groupCallHangup) , _stickedTooltipsShown(Core::App().settings().hiddenGroupCallTooltips() @@ -257,22 +260,34 @@ void Panel::initWindow() { return base::EventFilterResult::Cancel; } else if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) { - if (static_cast(e.get())->key() == Qt::Key_Space) { + const auto key = static_cast(e.get())->key(); + if (key == Qt::Key_Space) { _call->pushToTalk( e->type() == QEvent::KeyPress, kSpacePushToTalkDelay); + } else if (key == Qt::Key_Escape && _fullScreen.current()) { + toggleFullScreen(false); } } return base::EventFilterResult::Continue; }); + QObject::connect( + window()->windowHandle(), + &QWindow::windowStateChanged, + [=](Qt::WindowState state) { + _fullScreen = (state == Qt::WindowFullScreen); + }); + window()->setBodyTitleArea([=](QPoint widgetPoint) { using Flag = Ui::WindowTitleHitTestFlag; const auto titleRect = QRect( 0, 0, widget()->width(), - st::groupCallMembersTop); + (mode() == PanelMode::Wide + ? st::groupCallWideVideoTop + : st::groupCallMembersTop)); const auto moveable = (titleRect.contains(widgetPoint) && (!_menuToggle || !_menuToggle->geometry().contains(widgetPoint)) && (!_menu || !_menu->geometry().contains(widgetPoint)) @@ -365,7 +380,11 @@ void Panel::initControls() { !real->scheduleStartSubscribed()); } return; + } else if (_call->rtmp()) { + toggleFullScreen(!_fullScreen.current()); + return; } + const auto oldState = _call->muted(); const auto newState = (oldState == MuteState::ForceMuted) ? MuteState::RaisedHand @@ -450,6 +469,14 @@ void Panel::initControls() { refreshControlsBackground(); } +void Panel::toggleFullScreen(bool fullscreen) { + if (fullscreen) { + window()->showFullScreen(); + } else { + window()->showNormal(); + } +} + void Panel::refreshLeftButton() { const auto share = _call->scheduleDate() && _peer->isBroadcast() @@ -467,6 +494,7 @@ void Panel::refreshLeftButton() { _settings->setClickedCallback([=] { showBox(Box(SettingsBox, _call)); }); + trackControls(_trackControls, true); } const auto raw = _callShare ? _callShare.data() : _settings.data(); raw->show(); @@ -615,7 +643,8 @@ void Panel::setupRealMuteButtonState(not_null real) { real->scheduleDateValue(), real->scheduleStartSubscribedValue(), _call->canManageValue(), - _mode.value() + _mode.value(), + _fullScreen.value() ) | rpl::distinct_until_changed( ) | rpl::filter( _2 != GroupCall::InstanceState::TransitionToRtc @@ -625,9 +654,11 @@ void Panel::setupRealMuteButtonState(not_null real) { TimeId scheduleDate, bool scheduleStartSubscribed, bool canManage, - PanelMode mode) { + PanelMode mode, + bool fullScreen) { const auto wide = (mode == PanelMode::Wide); using Type = Ui::CallMuteButtonType; + using ExpandType = Ui::CallMuteButtonExpandType; _mute->setState(Ui::CallMuteButtonState{ .text = (wide ? QString() @@ -664,6 +695,11 @@ void Panel::setupRealMuteButtonState(not_null real) { : mute == MuteState::Muted ? Type::Muted : Type::Active), + .expandType = ((scheduleDate || !_call->rtmp()) + ? ExpandType::None + : fullScreen + ? ExpandType::Expanded + : ExpandType::Normal), }); }, _callLifetime); } @@ -860,8 +896,10 @@ void Panel::minimizeVideo() { } const auto available = window()->screen()->availableGeometry(); const auto width = st::groupCallWidth; - const auto height = st::groupCallHeight; - auto geometry = QRect( + const auto height = _call->rtmp() + ? st::groupCallHeightRtmpMin + : st::groupCallHeight; + const auto geometry = QRect( window()->x() + (window()->width() - width) / 2, window()->y() + (window()->height() - height) / 2, width, @@ -1277,13 +1315,14 @@ void Panel::showMainMenu() { if (wide) { _wideMenu->installEventFilter(_menu); + trackControl(_menu, _trackControlsMenuLifetime); + const auto x = st::groupCallWideMenuPosition.x(); const auto y = st::groupCallWideMenuPosition.y(); _menu->moveToLeft( _wideMenu->x() + x, _wideMenu->y() - _menu->height() + y); _menu->showAnimated(Ui::PanelAnimation::Origin::BottomLeft); - trackControl(_menu, _trackControlsMenuLifetime); } else { _menuToggle->installEventFilter(_menu); const auto x = st::groupCallMenuPosition.x(); @@ -1404,7 +1443,10 @@ rpl::lifetime &Panel::lifetime() { void Panel::initGeometry() { const auto center = Core::App().getPointForCallPanelCenter(); - const auto rect = QRect(0, 0, st::groupCallWidth, st::groupCallHeight); + const auto height = (_call->rtmp() && !_call->canManage()) + ? st::groupCallHeightRtmpMin + : st::groupCallHeight; + const auto rect = QRect(0, 0, st::groupCallWidth, height); window()->setGeometry(rect.translated(center - rect.center())); window()->setMinimumSize(rect.size()); window()->show(); @@ -1854,15 +1896,14 @@ void Panel::updateTooltipGeometry() { _niceTooltip->pointAt(geometry, RectPart::Top, countPosition); } -void Panel::trackControls(bool track) { - if (_trackControls == track) { +void Panel::trackControls(bool track, bool force) { + if (!force && _trackControls == track) { return; } _trackControls = track; + _trackControlsOverStateLifetime.destroy(); + _trackControlsMenuLifetime.destroy(); if (!track) { - _trackControlsLifetime.destroy(); - _trackControlsOverStateLifetime.destroy(); - _trackControlsMenuLifetime.destroy(); toggleWideControls(true); if (_wideControlsAnimation.animating()) { _wideControlsAnimation.stop(); @@ -1932,12 +1973,13 @@ void Panel::updateButtonsGeometry() { Assert(_settings != nullptr); Assert(_callShare == nullptr); + const auto rtmp = _call->rtmp(); const auto shown = _wideControlsAnimation.value( _wideControlsShown ? 1. : 0.); const auto hidden = (shown == 0.); if (_viewport) { - _viewport->setControlsShown(_call->rtmp() ? 0. : shown); + _viewport->setControlsShown(rtmp ? 0. : shown); } const auto buttonsTop = widget()->height() - anim::interpolate( @@ -1947,10 +1989,10 @@ void Panel::updateButtonsGeometry() { const auto addSkip = st::callMuteButtonSmall.active.outerRadius; const auto muteSize = _mute->innerSize().width() + 2 * addSkip; const auto skip = st::groupCallButtonSkipSmall; - const auto fullWidth = (_video->width() + skip) - + (_screenShare->width() + skip) + const auto fullWidth = (rtmp ? 0 : (_video->width() + skip)) + + (rtmp ? 0 : (_screenShare->width() + skip)) + (muteSize + skip) - + (_settings ->width() + skip) + + (_settings->width() + skip) + _hangup->width(); const auto membersSkip = st::groupCallNarrowSkip; const auto membersWidth = _call->rtmp() @@ -1960,12 +2002,20 @@ void Panel::updateButtonsGeometry() { - membersWidth - membersSkip - fullWidth) / 2; - toggle(_screenShare, !hidden); - _screenShare->moveToLeft(left, buttonsTop); - left += _screenShare->width() + skip; - toggle(_video, !hidden); - _video->moveToLeft(left, buttonsTop); - left += _video->width() + skip; + toggle(_screenShare, !hidden && !rtmp); + if (!rtmp) { + _screenShare->moveToLeft(left, buttonsTop); + left += _screenShare->width() + skip; + } + toggle(_video, !hidden && !rtmp); + if (!rtmp) { + _video->moveToLeft(left, buttonsTop); + left += _video->width() + skip; + } else { + _wideMenu->moveToLeft(left, buttonsTop); + _settings->moveToLeft(left, buttonsTop); + left += _settings->width() + skip; + } toggle(_mute, !hidden); _mute->moveInner({ left + addSkip, buttonsTop + addSkip }); left += muteSize + skip; @@ -1973,9 +2023,11 @@ void Panel::updateButtonsGeometry() { || _call->showChooseJoinAs(); toggle(_settings, !hidden && !wideMenuShown); toggle(_wideMenu, !hidden && wideMenuShown); - _wideMenu->moveToLeft(left, buttonsTop); - _settings->moveToLeft(left, buttonsTop); - left += _settings->width() + skip; + if (!rtmp) { + _wideMenu->moveToLeft(left, buttonsTop); + _settings->moveToLeft(left, buttonsTop); + left += _settings->width() + skip; + } toggle(_hangup, !hidden); _hangup->moveToLeft(left, buttonsTop); left += _hangup->width(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 65c0f92ca..5ae61bf47 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -130,7 +130,8 @@ private: bool handleClose(); void startScheduledNow(); - void trackControls(bool track); + void toggleFullScreen(bool fullscreen); + void trackControls(bool track, bool force = false); void raiseControls(); void enlargeVideo(); void minimizeVideo(); @@ -197,6 +198,7 @@ private: Ui::GL::Window _window; const std::unique_ptr _layerBg; rpl::variable _mode; + rpl::variable _fullScreen = false; #ifndef Q_OS_MAC std::unique_ptr _controls; @@ -215,7 +217,6 @@ private: object_ptr _joinAsToggle = { nullptr }; object_ptr _members = { nullptr }; std::unique_ptr _viewport; - rpl::lifetime _trackControlsLifetime; rpl::lifetime _trackControlsOverStateLifetime; rpl::lifetime _trackControlsMenuLifetime; object_ptr _startsIn = { nullptr }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 70c68bec8..0d9241936 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -235,6 +235,7 @@ void SettingsBox( const auto peer = call->peer(); const auto state = box->lifetime().make_state(); const auto real = peer->groupCall(); + const auto rtmp = call->rtmp(); const auto id = call->id(); const auto goodReal = (real && real->id() == id); @@ -274,280 +275,280 @@ void SettingsBox( }), &st::groupCallCheckbox, &st::groupCallRadio)); }); - AddButtonWithLabel( - layout, - tr::lng_group_call_microphone(), - rpl::single( - CurrentAudioInputName() - ) | rpl::then( - state->inputNameStream.events() - ), - st::groupCallSettingsButton - )->addClickHandler([=] { - box->getDelegate()->show(ChooseAudioInputBox(crl::guard(box, [=]( - const QString &id, - const QString &name) { - state->inputNameStream.fire_copy(name); - if (state->micTester) { - state->micTester->setDeviceId(id); - } - }), &st::groupCallCheckbox, &st::groupCallRadio)); - }); - - state->micTestLevel = box->addRow( - object_ptr( - box.get(), - st::groupCallLevelMeter), - st::settingsLevelMeterPadding); - state->micTestLevel->resize(QSize(0, st::defaultLevelMeter.height)); - - state->levelUpdateTimer.setCallback([=] { - const auto was = state->micLevel; - state->micLevel = state->micTester->getAndResetLevel(); - state->micLevelAnimation.start([=] { - state->micTestLevel->setValue( - state->micLevelAnimation.value(state->micLevel)); - }, was, state->micLevel, kMicTestAnimationDuration); - }); - - AddSkip(layout); - //AddDivider(layout); - //AddSkip(layout); - - AddButton( - layout, - tr::lng_group_call_noise_suppression(), - st::groupCallSettingsButton - )->toggleOn(rpl::single( - settings.groupCallNoiseSuppression() - ))->toggledChanges( - ) | rpl::start_with_next([=](bool enabled) { - Core::App().settings().setGroupCallNoiseSuppression(enabled); - call->setNoiseSuppression(enabled); - Core::App().saveSettingsDelayed(); - }, layout->lifetime()); - - - using GlobalShortcut = base::GlobalShortcut; - struct PushToTalkState { - rpl::variable recordText = tr::lng_group_call_ptt_shortcut(); - rpl::variable shortcutText; - rpl::event_stream pushToTalkToggles; - std::shared_ptr manager; - GlobalShortcut shortcut; - crl::time delay = 0; - bool recording = false; - }; - if (base::GlobalShortcutsAvailable()) { - const auto state = box->lifetime().make_state(); - if (!base::GlobalShortcutsAllowed()) { - Core::App().settings().setGroupCallPushToTalk(false); - } - const auto tryFillFromManager = [=] { - state->shortcut = state->manager - ? state->manager->shortcutFromSerialized( - Core::App().settings().groupCallPushToTalkShortcut()) - : nullptr; - state->shortcutText = state->shortcut - ? state->shortcut->toDisplayString() - : QString(); - }; - state->manager = settings.groupCallPushToTalk() - ? call->ensureGlobalShortcutManager() - : nullptr; - tryFillFromManager(); - - state->delay = settings.groupCallPushToTalkDelay(); - const auto pushToTalk = AddButton( + if (!rtmp) { + AddButtonWithLabel( layout, - tr::lng_group_call_push_to_talk(), + tr::lng_group_call_microphone(), + rpl::single( + CurrentAudioInputName() + ) | rpl::then( + state->inputNameStream.events() + ), st::groupCallSettingsButton - )->toggleOn(rpl::single( - settings.groupCallPushToTalk() - ) | rpl::then(state->pushToTalkToggles.events())); - const auto pushToTalkWrap = layout->add( - object_ptr>( - layout, - object_ptr(layout))); - const auto pushToTalkInner = pushToTalkWrap->entity(); - const auto recording = AddButton( - pushToTalkInner, - state->recordText.value(), - st::groupCallSettingsButton); - CreateRightLabel( - recording, - state->shortcutText.value(), - st::groupCallSettingsButton, - state->recordText.value()); - - const auto applyAndSave = [=] { - call->applyGlobalShortcutChanges(); - Core::App().saveSettingsDelayed(); - }; - const auto showPrivacyRequest = [=] { -#ifdef Q_OS_MAC - if (!Platform::IsMac10_14OrGreater()) { - return; - } - const auto requestInputMonitoring = Platform::IsMac10_15OrGreater(); - box->getDelegate()->show(Box([=](not_null box) { - box->addRow( - object_ptr( - box.get(), - rpl::combine( - tr::lng_group_call_mac_access(), - (requestInputMonitoring - ? tr::lng_group_call_mac_input() - : tr::lng_group_call_mac_accessibility()) - ) | 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(), [=] { - if (requestInputMonitoring) { - Platform::OpenInputMonitoringPrivacySettings(); - } else { - Platform::OpenAccessibilityPrivacySettings(); - } - }); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); - - if (!requestInputMonitoring) { - // Accessibility is enabled without app restart, so short-poll it. - base::timer_each( - kCheckAccessibilityInterval - ) | rpl::filter([] { - return base::GlobalShortcutsAllowed(); - }) | rpl::start_with_next([=] { - box->closeBox(); - }, box->lifetime()); + )->addClickHandler([=] { + box->getDelegate()->show(ChooseAudioInputBox(crl::guard(box, [=]( + const QString &id, + const QString &name) { + state->inputNameStream.fire_copy(name); + if (state->micTester) { + state->micTester->setDeviceId(id); } - })); -#endif // Q_OS_MAC - }; - const auto ensureManager = [=] { - if (state->manager) { - return true; - } else if (base::GlobalShortcutsAllowed()) { - state->manager = call->ensureGlobalShortcutManager(); - tryFillFromManager(); - return true; - } - showPrivacyRequest(); - return false; - }; - const auto stopRecording = [=] { - state->recording = false; - state->recordText = tr::lng_group_call_ptt_shortcut(); - state->shortcutText = state->shortcut - ? state->shortcut->toDisplayString() - : QString(); - recording->setColorOverride(std::nullopt); - if (state->manager) { - state->manager->stopRecording(); - } - }; - const auto startRecording = [=] { - if (!ensureManager()) { - state->pushToTalkToggles.fire(false); - pushToTalkWrap->hide(anim::type::instant); - return; - } - state->recording = true; - state->recordText = tr::lng_group_call_ptt_recording(); - recording->setColorOverride( - st::groupCallSettingsAttentionButton.textFg->c); - auto progress = crl::guard(box, [=](GlobalShortcut shortcut) { - state->shortcutText = shortcut->toDisplayString(); - }); - auto done = crl::guard(box, [=](GlobalShortcut shortcut) { - state->shortcut = shortcut; - Core::App().settings().setGroupCallPushToTalkShortcut(shortcut - ? shortcut->serialize() - : QByteArray()); - applyAndSave(); - stopRecording(); - }); - state->manager->startRecording(std::move(progress), std::move(done)); - }; - recording->addClickHandler([=] { - if (state->recording) { - stopRecording(); - } else { - startRecording(); - } + }), &st::groupCallCheckbox, &st::groupCallRadio)); }); - const auto label = pushToTalkInner->add( - object_ptr( + state->micTestLevel = box->addRow( + object_ptr( + box.get(), + st::groupCallLevelMeter), + st::settingsLevelMeterPadding); + state->micTestLevel->resize(QSize(0, st::defaultLevelMeter.height)); + + state->levelUpdateTimer.setCallback([=] { + const auto was = state->micLevel; + state->micLevel = state->micTester->getAndResetLevel(); + state->micLevelAnimation.start([=] { + state->micTestLevel->setValue( + state->micLevelAnimation.value(state->micLevel)); + }, was, state->micLevel, kMicTestAnimationDuration); + }); + + AddSkip(layout); + //AddDivider(layout); + //AddSkip(layout); + + AddButton( + layout, + tr::lng_group_call_noise_suppression(), + st::groupCallSettingsButton + )->toggleOn(rpl::single( + settings.groupCallNoiseSuppression() + ))->toggledChanges( + ) | rpl::start_with_next([=](bool enabled) { + Core::App().settings().setGroupCallNoiseSuppression(enabled); + call->setNoiseSuppression(enabled); + Core::App().saveSettingsDelayed(); + }, layout->lifetime()); + + using GlobalShortcut = base::GlobalShortcut; + struct PushToTalkState { + rpl::variable recordText = tr::lng_group_call_ptt_shortcut(); + rpl::variable shortcutText; + rpl::event_stream pushToTalkToggles; + std::shared_ptr manager; + GlobalShortcut shortcut; + crl::time delay = 0; + bool recording = false; + }; + if (base::GlobalShortcutsAvailable()) { + const auto state = box->lifetime().make_state(); + if (!base::GlobalShortcutsAllowed()) { + Core::App().settings().setGroupCallPushToTalk(false); + } + const auto tryFillFromManager = [=] { + state->shortcut = state->manager + ? state->manager->shortcutFromSerialized( + Core::App().settings().groupCallPushToTalkShortcut()) + : nullptr; + state->shortcutText = state->shortcut + ? state->shortcut->toDisplayString() + : QString(); + }; + state->manager = settings.groupCallPushToTalk() + ? call->ensureGlobalShortcutManager() + : nullptr; + tryFillFromManager(); + + state->delay = settings.groupCallPushToTalkDelay(); + const auto pushToTalk = AddButton( + layout, + tr::lng_group_call_push_to_talk(), + st::groupCallSettingsButton + )->toggleOn(rpl::single( + settings.groupCallPushToTalk() + ) | rpl::then(state->pushToTalkToggles.events())); + const auto pushToTalkWrap = layout->add( + object_ptr>( + layout, + object_ptr(layout))); + const auto pushToTalkInner = pushToTalkWrap->entity(); + const auto recording = AddButton( pushToTalkInner, - st::groupCallDelayLabel), - st::groupCallDelayLabelMargin); - const auto value = std::clamp( - state->delay, - crl::time(0), - DelayByIndex(kDelaysCount - 1)); - const auto callback = [=](crl::time delay) { - state->delay = delay; - label->setText(tr::lng_group_call_ptt_delay( - tr::now, - lt_delay, - FormatDelay(delay))); - if (Core::App().settings().groupCallPushToTalkDelay() != delay) { - Core::App().settings().setGroupCallPushToTalkDelay(delay); + state->recordText.value(), + st::groupCallSettingsButton); + CreateRightLabel( + recording, + state->shortcutText.value(), + st::groupCallSettingsButton, + state->recordText.value()); + + const auto applyAndSave = [=] { + call->applyGlobalShortcutChanges(); + Core::App().saveSettingsDelayed(); + }; + const auto showPrivacyRequest = [=] { +#ifdef Q_OS_MAC + if (!Platform::IsMac10_14OrGreater()) { + return; + } + const auto requestInputMonitoring = Platform::IsMac10_15OrGreater(); + box->getDelegate()->show(Box([=](not_null box) { + box->addRow( + object_ptr( + box.get(), + rpl::combine( + tr::lng_group_call_mac_access(), + (requestInputMonitoring + ? tr::lng_group_call_mac_input() + : tr::lng_group_call_mac_accessibility()) + ) | 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(), [=] { + if (requestInputMonitoring) { + Platform::OpenInputMonitoringPrivacySettings(); + } else { + Platform::OpenAccessibilityPrivacySettings(); + } + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + + if (!requestInputMonitoring) { + // Accessibility is enabled without app restart, so short-poll it. + base::timer_each( + kCheckAccessibilityInterval + ) | rpl::filter([] { + return base::GlobalShortcutsAllowed(); + }) | rpl::start_with_next([=] { + box->closeBox(); + }, box->lifetime()); + } + })); +#endif // Q_OS_MAC + }; + const auto ensureManager = [=] { + if (state->manager) { + return true; + } else if (base::GlobalShortcutsAllowed()) { + state->manager = call->ensureGlobalShortcutManager(); + tryFillFromManager(); + return true; + } + showPrivacyRequest(); + return false; + }; + const auto stopRecording = [=] { + state->recording = false; + state->recordText = tr::lng_group_call_ptt_shortcut(); + state->shortcutText = state->shortcut + ? state->shortcut->toDisplayString() + : QString(); + recording->setColorOverride(std::nullopt); + if (state->manager) { + state->manager->stopRecording(); + } + }; + const auto startRecording = [=] { + if (!ensureManager()) { + state->pushToTalkToggles.fire(false); + pushToTalkWrap->hide(anim::type::instant); + return; + } + state->recording = true; + state->recordText = tr::lng_group_call_ptt_recording(); + recording->setColorOverride( + st::groupCallSettingsAttentionButton.textFg->c); + auto progress = crl::guard(box, [=](GlobalShortcut shortcut) { + state->shortcutText = shortcut->toDisplayString(); + }); + auto done = crl::guard(box, [=](GlobalShortcut shortcut) { + state->shortcut = shortcut; + Core::App().settings().setGroupCallPushToTalkShortcut(shortcut + ? shortcut->serialize() + : QByteArray()); + applyAndSave(); + stopRecording(); + }); + state->manager->startRecording(std::move(progress), std::move(done)); + }; + recording->addClickHandler([=] { + if (state->recording) { + stopRecording(); + } else { + startRecording(); + } + }); + + const auto label = pushToTalkInner->add( + object_ptr( + pushToTalkInner, + st::groupCallDelayLabel), + st::groupCallDelayLabelMargin); + const auto value = std::clamp( + state->delay, + crl::time(0), + DelayByIndex(kDelaysCount - 1)); + const auto callback = [=](crl::time delay) { + state->delay = delay; + label->setText(tr::lng_group_call_ptt_delay( + tr::now, + lt_delay, + FormatDelay(delay))); + if (Core::App().settings().groupCallPushToTalkDelay() != delay) { + Core::App().settings().setGroupCallPushToTalkDelay(delay); + applyAndSave(); + } + }; + callback(value); + const auto slider = pushToTalkInner->add( + object_ptr( + pushToTalkInner, + st::groupCallDelaySlider), + st::groupCallDelayMargin); + slider->resize(st::groupCallDelaySlider.seekSize); + slider->setPseudoDiscrete( + kDelaysCount, + DelayByIndex, + value, + callback); + + pushToTalkWrap->toggle( + settings.groupCallPushToTalk(), + anim::type::instant); + pushToTalk->toggledChanges( + ) | rpl::start_with_next([=](bool toggled) { + if (!toggled) { + stopRecording(); + } else if (!ensureManager()) { + state->pushToTalkToggles.fire(false); + pushToTalkWrap->hide(anim::type::instant); + return; + } + Core::App().settings().setGroupCallPushToTalk(toggled); applyAndSave(); - } - }; - callback(value); - const auto slider = pushToTalkInner->add( - object_ptr( - pushToTalkInner, - st::groupCallDelaySlider), - st::groupCallDelayMargin); - slider->resize(st::groupCallDelaySlider.seekSize); - slider->setPseudoDiscrete( - kDelaysCount, - DelayByIndex, - value, - callback); + pushToTalkWrap->toggle(toggled, anim::type::normal); + }, pushToTalk->lifetime()); - pushToTalkWrap->toggle( - settings.groupCallPushToTalk(), - anim::type::instant); - pushToTalk->toggledChanges( - ) | rpl::start_with_next([=](bool toggled) { - if (!toggled) { - stopRecording(); - } else if (!ensureManager()) { - state->pushToTalkToggles.fire(false); - pushToTalkWrap->hide(anim::type::instant); - return; - } - Core::App().settings().setGroupCallPushToTalk(toggled); - applyAndSave(); - pushToTalkWrap->toggle(toggled, anim::type::normal); - }, pushToTalk->lifetime()); + auto boxKeyFilter = [=](not_null e) { + return (e->type() == QEvent::KeyPress && state->recording) + ? base::EventFilterResult::Cancel + : base::EventFilterResult::Continue; + }; + box->lifetime().make_state>( + base::install_event_filter(box, std::move(boxKeyFilter))); + } - auto boxKeyFilter = [=](not_null e) { - return (e->type() == QEvent::KeyPress && state->recording) - ? base::EventFilterResult::Cancel - : base::EventFilterResult::Continue; - }; - box->lifetime().make_state>( - base::install_event_filter(box, std::move(boxKeyFilter))); + AddSkip(layout); + //AddDivider(layout); + //AddSkip(layout); } - - AddSkip(layout); - //AddDivider(layout); - //AddSkip(layout); - auto shareLink = Fn(); if (peer->isChannel() && peer->asChannel()->hasUsername() @@ -644,15 +645,17 @@ void SettingsBox( }); } - box->setShowFinishedCallback([=] { - // Means we finished showing the box. - crl::on_main(box, [=] { - state->micTester = std::make_unique( - Core::App().settings().callAudioBackend(), - Core::App().settings().callInputDeviceId()); - state->levelUpdateTimer.callEach(kMicTestUpdateInterval); + if (!rtmp) { + box->setShowFinishedCallback([=] { + // Means we finished showing the box. + crl::on_main(box, [=] { + state->micTester = std::make_unique( + Core::App().settings().callAudioBackend(), + Core::App().settings().callInputDeviceId()); + state->levelUpdateTimer.callEach(kMicTestUpdateInterval); + }); }); - }); + } box->setTitle(tr::lng_group_call_settings_title()); box->boxClosing( diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp index d7b72f0cc..2e01e14af 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp @@ -876,6 +876,9 @@ rpl::producer MuteButtonTooltip(not_null call) { // : tr::lng_group_call_set_reminder(); // }) | rpl::flatten_latest(); // } + if (call->rtmp()) { + return nullptr; + } return call->mutedValue( ) | rpl::map([](MuteState muted) { switch (muted) { diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 4a84e138e..47dc4fb1a 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -492,7 +492,7 @@ void GroupCall::processQueuedUpdates() { } void GroupCall::computeParticipantsCount() { - _fullCount = _allParticipantsLoaded + _fullCount = (_allParticipantsLoaded && !_listenersHidden) ? int(_participants.size()) : std::max(int(_participants.size()), _serverParticipantsCount); } diff --git a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp index fd9c0da57..6017bfbcd 100644 --- a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp +++ b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp @@ -825,7 +825,17 @@ void CallMuteButton::init() { ) | rpl::start_with_next([=](QRect clip) { Painter p(_content); - _icons[_iconState.index]->paint(p, _muteIconRect.x(), _muteIconRect.y()); + const auto expand = _state.current().expandType; + if (expand == CallMuteButtonExpandType::Expanded) { + st::callMuteFromFullScreen.paintInCenter(p, _muteIconRect); + } else if (expand == CallMuteButtonExpandType::Normal) { + st::callMuteToFullScreen.paintInCenter(p, _muteIconRect); + } else { + _icons[_iconState.index]->paint( + p, + _muteIconRect.x(), + _muteIconRect.y()); + } if (_radialInfo.state.has_value() && _switchAnimation.animating()) { const auto radialProgress = _radialInfo.realShowProgress; diff --git a/Telegram/SourceFiles/ui/controls/call_mute_button.h b/Telegram/SourceFiles/ui/controls/call_mute_button.h index cc6264438..f3d072638 100644 --- a/Telegram/SourceFiles/ui/controls/call_mute_button.h +++ b/Telegram/SourceFiles/ui/controls/call_mute_button.h @@ -43,11 +43,18 @@ enum class CallMuteButtonType { ScheduledNotify, }; +enum class CallMuteButtonExpandType { + None, + Normal, + Expanded, +}; + struct CallMuteButtonState { QString text; QString subtext; QString tooltip; CallMuteButtonType type = CallMuteButtonType::Connecting; + CallMuteButtonExpandType expandType = CallMuteButtonExpandType::None; }; class CallMuteButton final : private AbstractTooltipShower {