Add video/screencast pinned/unpinned toasts.

This commit is contained in:
John Preston 2021-05-31 17:10:30 +04:00
parent bcdfd2150d
commit ba6cee6f81
10 changed files with 298 additions and 180 deletions

View File

@ -287,6 +287,8 @@ PRIVATE
calls/group/calls_group_panel.h
calls/group/calls_group_settings.cpp
calls/group/calls_group_settings.h
calls/group/calls_group_toasts.cpp
calls/group/calls_group_toasts.h
calls/group/calls_group_viewport.cpp
calls/group/calls_group_viewport.h
calls/group/calls_group_viewport_opengl.cpp

View File

@ -2130,6 +2130,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_recording_started" = "Voice chat recording started.";
"lng_group_call_recording_stopped" = "Voice chat recording stopped.";
"lng_group_call_recording_saved" = "Audio saved to Saved Messages.";
"lng_group_call_pinned_camera_me" = "Your video is pinned.";
"lng_group_call_pinned_screen_me" = "Your screencast is pinned.";
"lng_group_call_pinned_camera" = "{user}'s video is pinned.";
"lng_group_call_pinned_screen" = "{user}'s screencast is pinned.";
"lng_group_call_unpinned_camera_me" = "Your video is unpinned.";
"lng_group_call_unpinned_screen_me" = "Your screencast is unpinned.";
"lng_group_call_unpinned_camera" = "{user}'s video is unpinned.";
"lng_group_call_unpinned_screen" = "{user}'s screencast is unpinned.";
"lng_group_call_sure_screencast" = "{user} is screensharing. This action will make your screencast pinned for all participants.";
"lng_group_call_recording_start_sure" = "Do you want to start recording this chat and save the result into an audio file?\n\nOther members will see the chat is being recorded.";
"lng_group_call_recording_stop_sure" = "Do you want to stop recording this chat?";
"lng_group_call_recording_start_field" = "Recording Title";

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_settings.h"
#include "calls/group/calls_group_menu.h"
#include "calls/group/calls_group_viewport.h"
#include "calls/group/calls_group_toasts.h"
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "ui/platform/ui_platform_window_title.h"
#include "ui/platform/ui_platform_utility.h"
@ -27,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/layer_manager.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "ui/round_rect.h"
#include "ui/special_buttons.h"
@ -68,7 +70,6 @@ constexpr auto kRecordingOpacity = 0.6;
constexpr auto kStartNoConfirmation = TimeId(10);
constexpr auto kControlsBackgroundOpacity = 0.8;
constexpr auto kOverrideActiveColorBgAlpha = 172;
constexpr auto kErrorDuration = 2 * crl::time(1000);
class InviteController final : public ParticipantsBoxController {
public:
@ -421,7 +422,8 @@ Panel::Panel(not_null<GroupCall*> call)
? Ui::CallMuteButtonType::ScheduledNotify
: Ui::CallMuteButtonType::ScheduledSilent),
}))
, _hangup(widget(), st::groupCallHangup) {
, _hangup(widget(), st::groupCallHangup)
, _toasts(std::make_unique<Toasts>(this)) {
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
_layerBg->setHideByBackgroundClick(true);
@ -441,7 +443,6 @@ Panel::Panel(not_null<GroupCall*> call)
initControls();
initLayout();
showAndActivate();
setupToasts();
}
Panel::~Panel() {
@ -456,12 +457,27 @@ void Panel::setupRealCallViewers() {
}, _window->lifetime());
}
not_null<GroupCall*> Panel::call() const {
return _call;
}
bool Panel::isActive() const {
return _window->isActiveWindow()
&& _window->isVisible()
&& !(_window->windowState() & Qt::WindowMinimized);
}
void Panel::showToast(TextWithEntities &&text, crl::time duration) {
if (const auto strong = _lastToast.get()) {
strong->hideAnimated();
}
_lastToast = Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = std::move(text),
.duration = duration,
});
}
void Panel::minimize() {
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
}
@ -814,19 +830,16 @@ void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
}
void Panel::initShareAction() {
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
const auto showBoxCallback = [=](object_ptr<Ui::BoxContent> next) {
_layerBg->showBox(std::move(next));
};
const auto showToast = [=](QString text) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { text },
});
const auto showToastCallback = [=](QString text) {
showToast({ text });
};
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
_peer,
showBox,
showToast);
showBoxCallback,
showToastCallback);
_callShareLinkCallback = [=, callback = std::move(shareLinkCallback)] {
if (_call->lookupReal()) {
callback();
@ -1199,118 +1212,6 @@ void Panel::toggleWideControls(bool shown) {
});
}
void Panel::setupToasts() {
setupJoinAsChangedToasts();
setupTitleChangedToasts();
setupRequestedToSpeakToasts();
setupAllowedToSpeakToasts();
setupErrorToasts();
}
void Panel::setupJoinAsChangedToasts() {
_call->rejoinEvents(
) | rpl::filter([](RejoinEvent event) {
return (event.wasJoinAs != event.nowJoinAs);
}) | rpl::map([=] {
return _call->stateValue() | rpl::filter([](State state) {
return (state == State::Joined);
}) | rpl::take(1);
}) | rpl::flatten_latest() | rpl::start_with_next([=] {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_join_as_changed(
tr::now,
lt_name,
Ui::Text::Bold(_call->joinAs()->name),
Ui::Text::WithEntities),
});
}, widget()->lifetime());
}
void Panel::setupTitleChangedToasts() {
_call->titleChanged(
) | rpl::filter([=] {
return (_call->lookupReal() != nullptr);
}) | rpl::map([=] {
return _peer->groupCall()->title().isEmpty()
? _peer->name
: _peer->groupCall()->title();
}) | rpl::start_with_next([=](const QString &title) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_title_changed(
tr::now,
lt_title,
Ui::Text::Bold(title),
Ui::Text::WithEntities),
});
}, widget()->lifetime());
}
void Panel::setupAllowedToSpeakToasts() {
_call->allowedToSpeakNotifications(
) | rpl::start_with_next([=] {
if (isActive()) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { tr::lng_group_call_can_speak_here(tr::now) },
});
} else {
const auto real = _call->lookupReal();
const auto name = (real && !real->title().isEmpty())
? real->title()
: _peer->name;
Ui::ShowMultilineToast({
.text = tr::lng_group_call_can_speak(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
});
}
}, widget()->lifetime());
}
void Panel::setupRequestedToSpeakToasts() {
_call->mutedValue(
) | rpl::combine_previous(
) | rpl::start_with_next([=](MuteState was, MuteState now) {
if (was == MuteState::ForceMuted && now == MuteState::RaisedHand) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_tooltip_raised_hand(tr::now),
});
}
}, widget()->lifetime());
}
void Panel::setupErrorToasts() {
_call->errors(
) | rpl::start_with_next([=](Error error) {
const auto key = [&] {
switch (error) {
case Error::NoCamera: return tr::lng_call_error_no_camera;
case Error::ScreenFailed:
return tr::lng_group_call_failed_screen;
case Error::MutedNoCamera:
return tr::lng_group_call_muted_no_camera;
case Error::MutedNoScreen:
return tr::lng_group_call_muted_no_screen;
case Error::DisabledNoCamera:
return tr::lng_group_call_chat_no_camera;
case Error::DisabledNoScreen:
return tr::lng_group_call_chat_no_screen;
}
Unexpected("Error in Calls::Group::Panel::setupErrorToasts.");
}();
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { key(tr::now) },
.duration = kErrorDuration,
});
}, widget()->lifetime());
}
void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
const auto validateRecordingMark = [=](bool recording) {
if (!recording && _recordingMark) {
@ -1328,10 +1229,7 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
const auto skip = st::groupCallRecordingMarkSkip;
_recordingMark->resize(size + 2 * skip, size + 2 * skip);
_recordingMark->setClickedCallback([=] {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { tr::lng_group_call_is_recorded(tr::now) },
});
showToast({ tr::lng_group_call_is_recorded(tr::now) });
});
const auto animate = [=] {
const auto opaque = state->opaque;
@ -1367,16 +1265,13 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool recorded) {
validateRecordingMark(recorded);
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = (recorded
? tr::lng_group_call_recording_started
: _call->recordingStoppedByMe()
? tr::lng_group_call_recording_saved
: tr::lng_group_call_recording_stopped)(
tr::now,
Ui::Text::RichLangValue),
});
showToast((recorded
? tr::lng_group_call_recording_started
: _call->recordingStoppedByMe()
? tr::lng_group_call_recording_saved
: tr::lng_group_call_recording_stopped)(
tr::now,
Ui::Text::RichLangValue));
}, widget()->lifetime());
validateRecordingMark(real->recordStartDate() != 0);
@ -1448,20 +1343,17 @@ void Panel::chooseJoinAs() {
const auto callback = [=](JoinInfo info) {
_call->rejoinAs(info);
};
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
const auto showBoxCallback = [=](object_ptr<Ui::BoxContent> next) {
_layerBg->showBox(std::move(next));
};
const auto showToast = [=](QString text) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { text },
});
const auto showToastCallback = [=](QString text) {
showToast({ text });
};
_joinAsProcess.start(
_peer,
context,
showBox,
showToast,
showBoxCallback,
showToastCallback,
callback,
_call->joinAs());
}
@ -1578,24 +1470,18 @@ void Panel::addMembers() {
}
const auto result = call->inviteUsers(users);
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities),
});
showToast(tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities));
} else if (const auto count = std::get_if<int>(&result)) {
if (*count > 0) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue),
});
showToast(tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue));
}
} else {
Unexpected("Result in GroupCall::inviteUsers.");

View File

@ -42,6 +42,9 @@ class ScrollArea;
class GenericBox;
class LayerManager;
class GroupCallScheduledLeft;
namespace Toast {
class Instance;
} // namespace Toast
namespace Platform {
class TitleControls;
} // namespace Platform
@ -54,6 +57,7 @@ struct CallBodyLayout;
namespace Calls::Group {
class Toasts;
class Members;
class Viewport;
enum class PanelMode;
@ -63,7 +67,11 @@ public:
Panel(not_null<GroupCall*> call);
~Panel();
[[nodiscard]] not_null<GroupCall*> call() const;
[[nodiscard]] bool isActive() const;
void showToast(TextWithEntities &&text, crl::time duration = 0);
void minimize();
void close();
void showAndActivate();
@ -88,12 +96,6 @@ private:
void setupScheduledLabels(rpl::producer<TimeId> date);
void setupMembers();
void setupVideo(not_null<Viewport*> viewport);
void setupToasts();
void setupJoinAsChangedToasts();
void setupTitleChangedToasts();
void setupRequestedToSpeakToasts();
void setupAllowedToSpeakToasts();
void setupErrorToasts();
void setupRealMuteButtonState(not_null<Data::GroupCall*> real);
bool handleClose();
@ -196,6 +198,9 @@ private:
object_ptr<Ui::ImportantTooltip> _niceTooltip = { nullptr };
Fn<void()> _callShareLinkCallback;
const std::unique_ptr<Toasts> _toasts;
base::weak_ptr<Ui::Toast::Instance> _lastToast;
rpl::lifetime _peerLifetime;
};

View File

@ -0,0 +1,175 @@
/*
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_toasts.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_panel.h"
#include "data/data_peer.h"
#include "data/data_group_call.h"
#include "ui/text/text_utilities.h"
#include "ui/toasts/common_toasts.h"
#include "lang/lang_keys.h"
namespace Calls::Group {
namespace {
constexpr auto kErrorDuration = 2 * crl::time(1000);
using State = GroupCall::State;
} // namespace
Toasts::Toasts(not_null<Panel*> panel)
: _panel(panel)
, _call(panel->call()) {
setup();
}
void Toasts::setup() {
setupJoinAsChanged();
setupTitleChanged();
setupRequestedToSpeak();
setupAllowedToSpeak();
setupPinnedVideo();
setupError();
}
void Toasts::setupJoinAsChanged() {
_call->rejoinEvents(
) | rpl::filter([](RejoinEvent event) {
return (event.wasJoinAs != event.nowJoinAs);
}) | rpl::map([=] {
return _call->stateValue() | rpl::filter([](State state) {
return (state == State::Joined);
}) | rpl::take(1);
}) | rpl::flatten_latest() | rpl::start_with_next([=] {
_panel->showToast(tr::lng_group_call_join_as_changed(
tr::now,
lt_name,
Ui::Text::Bold(_call->joinAs()->name),
Ui::Text::WithEntities));
}, _lifetime);
}
void Toasts::setupTitleChanged() {
_call->titleChanged(
) | rpl::filter([=] {
return (_call->lookupReal() != nullptr);
}) | rpl::map([=] {
const auto peer = _call->peer();
return peer->groupCall()->title().isEmpty()
? peer->name
: peer->groupCall()->title();
}) | rpl::start_with_next([=](const QString &title) {
_panel->showToast(tr::lng_group_call_title_changed(
tr::now,
lt_title,
Ui::Text::Bold(title),
Ui::Text::WithEntities));
}, _lifetime);
}
void Toasts::setupAllowedToSpeak() {
_call->allowedToSpeakNotifications(
) | rpl::start_with_next([=] {
if (_panel->isActive()) {
_panel->showToast({
tr::lng_group_call_can_speak_here(tr::now),
});
} else {
const auto real = _call->lookupReal();
const auto name = (real && !real->title().isEmpty())
? real->title()
: _call->peer()->name;
Ui::ShowMultilineToast({
.text = tr::lng_group_call_can_speak(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
});
}
}, _lifetime);
}
void Toasts::setupPinnedVideo() {
_call->videoEndpointPinnedValue(
) | rpl::map([=](bool pinned) {
return pinned
? _call->videoEndpointLargeValue()
: rpl::single(_call->videoEndpointLarge());
}) | rpl::flatten_latest(
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
const auto pinned = _call->videoEndpointPinned();
const auto peer = endpoint.peer;
if (!peer) {
return;
}
const auto text = [&] {
const auto me = (peer == _call->joinAs());
const auto camera = (endpoint.type == VideoEndpointType::Camera);
if (me) {
const auto key = camera
? (pinned
? tr::lng_group_call_pinned_camera_me
: tr::lng_group_call_unpinned_camera_me)
: (pinned
? tr::lng_group_call_pinned_screen_me
: tr::lng_group_call_unpinned_screen_me);
return key(tr::now);
}
const auto key = camera
? (pinned
? tr::lng_group_call_pinned_camera
: tr::lng_group_call_unpinned_camera)
: (pinned
? tr::lng_group_call_pinned_screen
: tr::lng_group_call_unpinned_screen);
return key(tr::now, lt_user, peer->shortName());
}();
_panel->showToast({ text });
}, _lifetime);
}
void Toasts::setupRequestedToSpeak() {
_call->mutedValue(
) | rpl::combine_previous(
) | rpl::start_with_next([=](MuteState was, MuteState now) {
if (was == MuteState::ForceMuted && now == MuteState::RaisedHand) {
_panel->showToast({
tr::lng_group_call_tooltip_raised_hand(tr::now),
});
}
}, _lifetime);
}
void Toasts::setupError() {
_call->errors(
) | rpl::start_with_next([=](Error error) {
const auto key = [&] {
switch (error) {
case Error::NoCamera: return tr::lng_call_error_no_camera;
case Error::ScreenFailed:
return tr::lng_group_call_failed_screen;
case Error::MutedNoCamera:
return tr::lng_group_call_muted_no_camera;
case Error::MutedNoScreen:
return tr::lng_group_call_muted_no_screen;
case Error::DisabledNoCamera:
return tr::lng_group_call_chat_no_camera;
case Error::DisabledNoScreen:
return tr::lng_group_call_chat_no_screen;
}
Unexpected("Error in Calls::Group::Toasts::setupErrorToasts.");
}();
_panel->showToast({ key(tr::now) }, kErrorDuration);
}, _lifetime);
}
} // namespace Calls::Group

View File

@ -0,0 +1,38 @@
/*
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
*/
#pragma once
namespace Calls {
class GroupCall;
} // namespace Calls
namespace Calls::Group {
class Panel;
class Toasts final {
public:
explicit Toasts(not_null<Panel*> panel);
private:
void setup();
void setupJoinAsChanged();
void setupTitleChanged();
void setupRequestedToSpeak();
void setupAllowedToSpeak();
void setupPinnedVideo();
void setupError();
const not_null<Panel*> _panel;
const not_null<GroupCall*> _call;
rpl::lifetime _lifetime;
};
} // namespace Calls::Group

View File

@ -1139,7 +1139,7 @@ void CallMuteButton::overridesColors(
const auto toInactive = IsInactive(toType);
const auto fromInactive = IsInactive(fromType);
if (toInactive && (progress == 1)) {
_colorOverrides.fire({ std::nullopt, std::nullopt });
_colorOverrides = CallButtonColors();
return;
}
const auto &fromStops = _colors.find(fromType)->second.stops;
@ -1158,11 +1158,11 @@ void CallMuteButton::overridesColors(
}
const auto resultBg = anim::color(from, to, progress);
const auto resultRipple = anim::color(fromRipple, toRipple, progress);
_colorOverrides.fire({ resultBg, resultRipple });
_colorOverrides = CallButtonColors{ resultBg, resultRipple };
}
rpl::producer<CallButtonColors> CallMuteButton::colorOverrides() const {
return _colorOverrides.events();
return _colorOverrides.value();
}
not_null<RpWidget*> CallMuteButton::outer() const {

View File

@ -11,6 +11,7 @@
#include "ui/effects/cross_line.h"
#include "ui/effects/gradient.h"
#include "ui/effects/radial_animation.h"
#include "ui/widgets/call_button.h"
#include "ui/widgets/tooltip.h"
#include "lottie/lottie_icon.h"
@ -31,8 +32,6 @@ class FlatLabel;
class RpWidget;
class AnimatedLabel;
struct CallButtonColors;
enum class CallMuteButtonType {
Connecting,
Active,
@ -179,7 +178,7 @@ private:
Animations::Simple _switchAnimation;
Animations::Simple _shakeAnimation;
rpl::event_stream<CallButtonColors> _colorOverrides;
rpl::variable<CallButtonColors> _colorOverrides;
};

View File

@ -12,7 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
void ShowMultilineToast(MultilineToastArgs &&args) {
base::weak_ptr<Toast::Instance> ShowMultilineToast(
MultilineToastArgs &&args) {
auto config = Ui::Toast::Config{
.text = std::move(args.text),
.st = &st::defaultMultilineToast,
@ -21,11 +22,9 @@ void ShowMultilineToast(MultilineToastArgs &&args) {
: Ui::Toast::kDefaultDuration),
.multiline = true,
};
if (args.parentOverride) {
Ui::Toast::Show(args.parentOverride, std::move(config));
} else {
Ui::Toast::Show(std::move(config));
}
return args.parentOverride
? Ui::Toast::Show(args.parentOverride, std::move(config))
: Ui::Toast::Show(std::move(config));
}
} // namespace Ui

View File

@ -8,8 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/text/text_entity.h"
#include "base/weak_ptr.h"
namespace Ui {
namespace Toast {
class Instance;
} // namespace Toast
struct MultilineToastArgs {
QWidget *parentOverride = nullptr;
@ -17,6 +21,7 @@ struct MultilineToastArgs {
crl::time duration = 0;
};
void ShowMultilineToast(MultilineToastArgs &&args);
base::weak_ptr<Toast::Instance> ShowMultilineToast(
MultilineToastArgs &&args);
} // namespace Ui