Guarded users from instant calls with confirmation dialog.

This commit is contained in:
23rd 2023-02-27 18:30:50 +03:00 committed by John Preston
parent 30ae879fb3
commit df176dd1d9
7 changed files with 102 additions and 23 deletions

View file

@ -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";

View file

@ -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) {

View file

@ -220,7 +220,7 @@ Call::Call(
std::make_unique<Webrtc::VideoTrack>(
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, [=] {

View file

@ -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<UserData*> _user;
MTP::Sender _api;
Type _type = Type::Outgoing;
rpl::variable<State> _state = State::Starting;
rpl::variable<State> _state = State::WaitingUserConfirmation;
rpl::variable<RemoteAudioState> _remoteAudioState =
RemoteAudioState::Active;
rpl::variable<Webrtc::VideoState> _remoteVideoState;

View file

@ -341,9 +341,14 @@ void Instance::createCall(not_null<UserData*> user, Call::Type type, bool video)
_currentCallPanel = std::make_unique<Panel>(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<GroupCall*> call) {

View file

@ -73,9 +73,22 @@ Panel::Panel(not_null<Call*> call)
, _answerHangupRedial(widget(), st::callAnswer, &st::callHangup)
, _decline(widget(), object_ptr<Ui::CallButton>(widget(), st::callHangup))
, _cancel(widget(), object_ptr<Ui::CallButton>(widget(), st::callCancel))
, _screencast(widget(), st::callScreencastOn, &st::callScreencastOff)
, _screencast(
widget(),
object_ptr<Ui::CallButton>(
widget(),
st::callScreencastOn,
&st::callScreencastOff))
, _camera(widget(), st::callCameraMute, &st::callCameraUnmute)
, _mute(widget(), st::callMicrophoneMute, &st::callMicrophoneUnmute)
, _startVideo(
widget(),
object_ptr<Ui::CallButton>(widget(), st::callStartVideo))
, _mute(
widget(),
object_ptr<Ui::CallButton>(
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<bool> 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<base::PowerSaveBlocker>(
@ -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()");
};

View file

@ -77,6 +77,8 @@ public:
bool withAudio) override;
void chooseSourceStop() override;
[[nodiscard]] rpl::producer<bool> startOutgoingRequests() const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
@ -87,6 +89,7 @@ private:
Answer,
Hangup,
Redial,
StartCall,
};
[[nodiscard]] not_null<Ui::RpWindow*> window() const;
@ -147,9 +150,10 @@ private:
bool _outgoingPreviewInBody = false;
std::optional<AnswerHangupRedialState> _answerHangupRedialState;
Ui::Animations::Simple _hangupShownProgress;
object_ptr<Ui::CallButton> _screencast;
object_ptr<Ui::FadeWrap<Ui::CallButton>> _screencast;
object_ptr<Ui::CallButton> _camera;
object_ptr<Ui::CallButton> _mute;
object_ptr<Ui::FadeWrap<Ui::CallButton>> _startVideo;
object_ptr<Ui::FadeWrap<Ui::CallButton>> _mute;
object_ptr<Ui::FlatLabel> _name;
object_ptr<Ui::FlatLabel> _status;
object_ptr<Ui::RpWidget> _fingerprint = { nullptr };