From df176dd1d9335be81c21e48284b6d632be5da832 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 27 Feb 2023 18:30:50 +0300 Subject: [PATCH] Guarded users from instant calls with confirmation dialog. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/calls/calls.style | 10 ++- Telegram/SourceFiles/calls/calls_call.cpp | 8 +- Telegram/SourceFiles/calls/calls_call.h | 4 +- Telegram/SourceFiles/calls/calls_instance.cpp | 9 +- Telegram/SourceFiles/calls/calls_panel.cpp | 84 +++++++++++++++---- Telegram/SourceFiles/calls/calls_panel.h | 8 +- 7 files changed, 102 insertions(+), 23 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 26977a32d..23d7a0c9c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2572,6 +2572,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_status_failed" = "failed to connect"; "lng_call_status_ringing" = "ringing..."; "lng_call_status_busy" = "line busy"; +"lng_call_status_sure" = "Click on the Camera icon if you want to start a video call."; "lng_call_fingerprint_tooltip" = "If emoji on {user}'s screen are the same, this call is 100% secure"; "lng_call_error_not_available" = "Sorry, {user} doesn't accept calls."; @@ -2612,6 +2613,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_rate_label" = "Please rate the quality of your call"; "lng_call_rate_comment" = "Comment (optional)"; +"lng_call_start" = "Start Call"; "lng_call_start_video" = "Start Video"; "lng_call_stop_video" = "Stop Video"; "lng_call_screencast" = "Screencast"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 8fc8445b3..28b5f90a6 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -117,6 +117,14 @@ callAnswer: CallButton { outerBg: callAnswerBgOuter; label: callButtonLabel; } +callStartVideo: CallButton(callAnswer) { + button: IconButton(callButton) { + icon: icon {{ "calls/call_camera_active", callIconFg }}; + ripple: RippleAnimation(defaultRippleAnimation) { + color: callAnswerRipple; + } + } +} callHangup: CallButton(callAnswer) { button: IconButton(callButton) { icon: icon {{ "calls/call_discard", callIconFg }}; @@ -292,7 +300,7 @@ callName: FlatLabel(defaultFlatLabel) { } callStatus: FlatLabel(defaultFlatLabel) { minWidth: 260px; - maxHeight: 20px; + maxHeight: 60px; textFg: callStatusFg; align: align(top); style: TextStyle(defaultTextStyle) { diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 64fa5d98a..92bf19b69 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -220,7 +220,7 @@ Call::Call( std::make_unique( StartVideoState(video))) { if (_type == Type::Outgoing) { - setState(State::Requesting); + setState(State::WaitingUserConfirmation); } else { const auto &config = _user->session().serverConfig(); _discardByTimeoutTimer.callOnce(config.callRingTimeoutMs); @@ -344,6 +344,12 @@ void Call::startIncoming() { }).send(); } +void Call::applyUserConfirmation() { + if (_state.current() == State::WaitingUserConfirmation) { + setState(State::Requesting); + } +} + void Call::answer() { const auto video = isSharingVideo(); _delegate->callRequestPermissionsOrFail(crl::guard(this, [=] { diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index ffc503d1f..f6e05c7b1 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -126,6 +126,7 @@ public: WaitingIncoming, Ringing, Busy, + WaitingUserConfirmation, }; [[nodiscard]] State state() const { return _state.current(); @@ -179,6 +180,7 @@ public: crl::time getDurationMs() const; float64 getWaitingSoundPeakValue() const; + void applyUserConfirmation(); void answer(); void hangup(); void redial(); @@ -257,7 +259,7 @@ private: const not_null _user; MTP::Sender _api; Type _type = Type::Outgoing; - rpl::variable _state = State::Starting; + rpl::variable _state = State::WaitingUserConfirmation; rpl::variable _remoteAudioState = RemoteAudioState::Active; rpl::variable _remoteVideoState; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 405972c2c..230d862b2 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -341,9 +341,14 @@ void Instance::createCall(not_null user, Call::Type type, bool video) _currentCallPanel = std::make_unique(raw); _currentCall = std::move(call); } + _currentCallPanel->startOutgoingRequests( + ) | rpl::start_with_next([=](bool video) { + raw->applyUserConfirmation(); + raw->toggleCameraSharing(video); + refreshServerConfig(&user->session()); + refreshDhConfig(); + }, raw->lifetime()); _currentCallChanges.fire_copy(raw); - refreshServerConfig(&user->session()); - refreshDhConfig(); } void Instance::destroyGroupCall(not_null call) { diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index e1ac777bc..6f116555f 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -73,9 +73,22 @@ Panel::Panel(not_null call) , _answerHangupRedial(widget(), st::callAnswer, &st::callHangup) , _decline(widget(), object_ptr(widget(), st::callHangup)) , _cancel(widget(), object_ptr(widget(), st::callCancel)) -, _screencast(widget(), st::callScreencastOn, &st::callScreencastOff) +, _screencast( + widget(), + object_ptr( + widget(), + st::callScreencastOn, + &st::callScreencastOff)) , _camera(widget(), st::callCameraMute, &st::callCameraUnmute) -, _mute(widget(), st::callMicrophoneMute, &st::callMicrophoneUnmute) +, _startVideo( + widget(), + object_ptr(widget(), st::callStartVideo)) +, _mute( + widget(), + object_ptr( + widget(), + st::callMicrophoneMute, + &st::callMicrophoneUnmute)) , _name(widget(), st::callName) , _status(widget(), st::callStatus) { _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox); @@ -214,12 +227,12 @@ void Panel::initWidget() { void Panel::initControls() { _hangupShown = (_call->type() == Type::Outgoing); - _mute->setClickedCallback([=] { + _mute->entity()->setClickedCallback([=] { if (_call) { _call->setMuted(!_call->muted()); } }); - _screencast->setClickedCallback([=] { + _screencast->entity()->setClickedCallback([=] { if (!_call) { return; } else if (!Webrtc::DesktopCaptureAllowed()) { @@ -267,6 +280,7 @@ void Panel::initControls() { _call->redial(); } else if (_call->isIncomingWaiting()) { _call->answer(); + } else if (state == State::WaitingUserConfirmation) { } else { _call->hangup(); } @@ -278,6 +292,7 @@ void Panel::initControls() { }; _decline->entity()->setClickedCallback(hangupCallback); _cancel->entity()->setClickedCallback(hangupCallback); + _startVideo->entity()->setText(tr::lng_call_start_video()); reinitWithCall(_call); @@ -318,6 +333,17 @@ rpl::lifetime &Panel::chooseSourceInstanceLifetime() { return lifetime(); } +rpl::producer Panel::startOutgoingRequests() const { + const auto filter = [=] { + return _call && (_call->state() == State::WaitingUserConfirmation); + }; + return rpl::merge( + _startVideo->entity()->clicks( + ) | rpl::filter(filter) | rpl::map_to(true), + _answerHangupRedial->clicks( + ) | rpl::filter(filter) | rpl::map_to(false)); +} + void Panel::chooseSourceAccepted( const QString &deviceId, bool withAudio) { @@ -388,8 +414,8 @@ void Panel::reinitWithCall(Call *call) { _call->mutedValue( ) | rpl::start_with_next([=](bool mute) { - _mute->setProgress(mute ? 1. : 0.); - _mute->setText(mute + _mute->entity()->setProgress(mute ? 1. : 0.); + _mute->entity()->setText(mute ? tr::lng_call_unmute_audio() : tr::lng_call_mute_audio()); }, _callLifetime); @@ -405,8 +431,8 @@ void Panel::reinitWithCall(Call *call) { } { const auto active = _call->isSharingScreen(); - _screencast->setProgress(active ? 0. : 1.); - _screencast->setText(tr::lng_call_screencast()); + _screencast->entity()->setProgress(active ? 0. : 1.); + _screencast->entity()->setText(tr::lng_call_screencast()); _outgoingVideoBubble->setMirrored(!active); } }, _callLifetime); @@ -497,6 +523,7 @@ void Panel::reinitWithCall(Call *call) { _decline->raise(); _cancel->raise(); _camera->raise(); + _startVideo->raise(); _mute->raise(); _powerSaveBlocker = std::make_unique( @@ -755,7 +782,10 @@ void Panel::updateHangupGeometry() { auto threeWidth = twoWidth + st::callCancel.button.width; auto rightFrom = (widget()->width() - threeWidth) / 2; auto rightTo = (widget()->width() - twoWidth) / 2; - auto hangupProgress = _hangupShownProgress.value(_hangupShown ? 1. : 0.); + auto hangupProgress = (_call + && _call->state() == State::WaitingUserConfirmation) + ? 0. + : _hangupShownProgress.value(_hangupShown ? 1. : 0.); auto hangupRight = anim::interpolate(rightFrom, rightTo, hangupProgress); _answerHangupRedial->moveToRight(hangupRight, _buttonsTop); _answerHangupRedial->setProgress(hangupProgress); @@ -764,6 +794,9 @@ void Panel::updateHangupGeometry() { _camera->moveToLeft( hangupRight - _mute->width() + _screencast->width(), _buttonsTop); + if (_startVideo->toggled()) { + _startVideo->moveToLeft(_camera->x(), _camera->y()); + } } void Panel::updateStatusGeometry() { @@ -811,33 +844,50 @@ void Panel::stateChanged(State state) { && (state != State::EndedByOtherDevice) && (state != State::FailedHangingUp) && (state != State::Failed)) { - if (state == State::Busy) { + const auto isBusy = (state == State::Busy); + const auto isWaitingUser = (state == State::WaitingUserConfirmation); + if (isBusy) { _powerSaveBlocker = nullptr; } + if (_startVideo->toggled() && !isWaitingUser) { + _startVideo->toggle(false, anim::type::instant); + } else if (!_startVideo->toggled() && isWaitingUser) { + _startVideo->toggle(true, anim::type::instant); + } + _camera->setVisible(!_startVideo->toggled()); - auto toggleButton = [&](auto &&button, bool visible) { + const auto toggleButton = [&](auto &&button, bool visible) { button->toggle( visible, window()->isHidden() ? anim::type::instant : anim::type::normal); }; - auto incomingWaiting = _call->isIncomingWaiting(); + const auto incomingWaiting = _call->isIncomingWaiting(); if (incomingWaiting) { _updateOuterRippleTimer.callEach(Call::kSoundSampleMs); } toggleButton(_decline, incomingWaiting); - toggleButton(_cancel, (state == State::Busy)); - auto hangupShown = !_decline->toggled() + toggleButton(_cancel, (isBusy || isWaitingUser)); + toggleButton(_mute, !isWaitingUser); + toggleButton(_screencast, !isWaitingUser); + const auto hangupShown = !_decline->toggled() && !_cancel->toggled(); if (_hangupShown != hangupShown) { _hangupShown = hangupShown; - _hangupShownProgress.start([this] { updateHangupGeometry(); }, _hangupShown ? 0. : 1., _hangupShown ? 1. : 0., st::callPanelDuration, anim::sineInOut); + _hangupShownProgress.start( + [this] { updateHangupGeometry(); }, + _hangupShown ? 0. : 1., + _hangupShown ? 1. : 0., + st::callPanelDuration, + anim::sineInOut); } const auto answerHangupRedialState = incomingWaiting ? AnswerHangupRedialState::Answer - : (state == State::Busy) + : isBusy ? AnswerHangupRedialState::Redial + : isWaitingUser + ? AnswerHangupRedialState::StartCall : AnswerHangupRedialState::Hangup; if (_answerHangupRedialState != answerHangupRedialState) { _answerHangupRedialState = answerHangupRedialState; @@ -860,6 +910,7 @@ void Panel::refreshAnswerHangupRedialLabel() { case AnswerHangupRedialState::Answer: return tr::lng_call_accept(); case AnswerHangupRedialState::Hangup: return tr::lng_call_end_call(); case AnswerHangupRedialState::Redial: return tr::lng_call_redial(); + case AnswerHangupRedialState::StartCall: return tr::lng_call_start(); } Unexpected("AnswerHangupRedialState value."); }()); @@ -891,6 +942,7 @@ void Panel::updateStatusText(State state) { case State::WaitingIncoming: return tr::lng_call_status_incoming(tr::now); case State::Ringing: return tr::lng_call_status_ringing(tr::now); case State::Busy: return tr::lng_call_status_busy(tr::now); + case State::WaitingUserConfirmation: return tr::lng_call_status_sure(tr::now); } Unexpected("State in stateChanged()"); }; diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index f891835c0..e7f0ac77f 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -77,6 +77,8 @@ public: bool withAudio) override; void chooseSourceStop() override; + [[nodiscard]] rpl::producer startOutgoingRequests() const; + [[nodiscard]] rpl::lifetime &lifetime(); private: @@ -87,6 +89,7 @@ private: Answer, Hangup, Redial, + StartCall, }; [[nodiscard]] not_null window() const; @@ -147,9 +150,10 @@ private: bool _outgoingPreviewInBody = false; std::optional _answerHangupRedialState; Ui::Animations::Simple _hangupShownProgress; - object_ptr _screencast; + object_ptr> _screencast; object_ptr _camera; - object_ptr _mute; + object_ptr> _startVideo; + object_ptr> _mute; object_ptr _name; object_ptr _status; object_ptr _fingerprint = { nullptr };