2
0
mirror of https://github.com/kotatogram/kotatogram-desktop synced 2025-08-31 14:45:14 +00:00

Added ability to start video recording in group calls.

This commit is contained in:
23rd
2021-09-07 14:33:20 +03:00
committed by John Preston
parent 19611d5b26
commit cd59ba6629
16 changed files with 593 additions and 95 deletions

View File

@@ -1314,3 +1314,14 @@ groupCallStickedTooltipClose: IconButton(defaultIconButton) {
}
groupCallNiceTooltipTop: 4px;
groupCallPaused: icon {{ "calls/video_large_paused", groupCallVideoTextFg }};
groupCallRecordingSubLabel: FlatLabel(boxDividerLabel) {
margin: margins(0px, 0px, 0px, 0px);
textFg: groupCallMemberNotJoinedStatus;
align: align(top);
}
groupCallRecordingInfoMargins: margins(0px, 22px, 0px, 22px);
groupCallRecordingSubLabelMargins: margins(8px, 22px, 8px, 22px);
groupCallRecordingAudioSkip: 23px;
groupCallRecordingSelectWidth: 2px;
groupCallRecordingInfoHeight: 204px;

View File

@@ -2184,7 +2184,11 @@ void GroupCall::changeTitle(const QString &title) {
}).send();
}
void GroupCall::toggleRecording(bool enabled, const QString &title) {
void GroupCall::toggleRecording(
bool enabled,
const QString &title,
bool video,
bool videoPortrait) {
const auto real = lookupReal();
if (!real) {
return;
@@ -2201,10 +2205,11 @@ void GroupCall::toggleRecording(bool enabled, const QString &title) {
using Flag = MTPphone_ToggleGroupCallRecord::Flag;
_api.request(MTPphone_ToggleGroupCallRecord(
MTP_flags((enabled ? Flag::f_start : Flag(0))
| (video ? Flag::f_video : Flag(0))
| (title.isEmpty() ? Flag(0) : Flag::f_title)),
inputCall(),
MTP_string(title),
MTPBool() // video_portrait
MTP_bool(videoPortrait)
)).done([=](const MTPUpdates &result) {
_peer->session().api().applyUpdates(result);
_recordingStoppedByMe = false;

View File

@@ -245,7 +245,11 @@ public:
void handlePossibleCreateOrJoinResponse(
const MTPDupdateGroupCallConnection &data);
void changeTitle(const QString &title);
void toggleRecording(bool enabled, const QString &title);
void toggleRecording(
bool enabled,
const QString &title,
bool video,
bool videoPortrait);
[[nodiscard]] bool recordingStoppedByMe() const {
return _recordingStoppedByMe;
}

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_settings.h"
#include "calls/group/calls_group_panel.h"
#include "calls/group/ui/calls_group_recording_box.h"
#include "data/data_peer.h"
#include "data/data_group_call.h"
#include "info/profile/info_profile_values.h" // Info::Profile::NameValue.
@@ -31,87 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Calls::Group {
namespace {
constexpr auto kMaxGroupCallLength = 40;
void EditGroupCallTitleBox(
not_null<Ui::GenericBox*> box,
const QString &placeholder,
const QString &title,
bool livestream,
Fn<void(QString)> done) {
box->setTitle(livestream
? tr::lng_group_call_edit_title_channel()
: tr::lng_group_call_edit_title());
const auto input = box->addRow(object_ptr<Ui::InputField>(
box,
st::groupCallField,
rpl::single(placeholder),
title));
input->setMaxLength(kMaxGroupCallLength);
box->setFocusCallback([=] {
input->setFocusFast();
});
const auto submit = [=] {
const auto result = input->getLastText().trimmed();
box->closeBox();
done(result);
};
QObject::connect(input, &Ui::InputField::submitted, submit);
box->addButton(tr::lng_settings_save(), submit);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
void StartGroupCallRecordingBox(
not_null<Ui::GenericBox*> box,
const QString &title,
Fn<void(QString)> done) {
box->setTitle(tr::lng_group_call_recording_start());
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_group_call_recording_start_sure(),
st::groupCallBoxLabel));
const auto input = box->addRow(object_ptr<Ui::InputField>(
box,
st::groupCallField,
tr::lng_group_call_recording_start_field(),
title));
box->setFocusCallback([=] {
input->setFocusFast();
});
const auto submit = [=] {
const auto result = input->getLastText().trimmed();
box->closeBox();
done(result);
};
QObject::connect(input, &Ui::InputField::submitted, submit);
box->addButton(tr::lng_group_call_recording_start_button(), submit);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
void StopGroupCallRecordingBox(
not_null<Ui::GenericBox*> box,
Fn<void(QString)> done) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_group_call_recording_stop_sure(),
st::groupCallBoxLabel),
style::margins(
st::boxRowPadding.left(),
st::boxPadding.top(),
st::boxRowPadding.right(),
st::boxPadding.bottom()));
box->addButton(tr::lng_box_ok(), [=] {
box->closeBox();
done(QString());
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
class JoinAsAction final : public Ui::Menu::ItemBase {
public:
JoinAsAction(
@@ -632,10 +552,15 @@ void FillMenu(
if (!real) {
return;
}
const auto type = std::make_shared<RecordingType>();
const auto recordStartDate = real->recordStartDate();
const auto done = [=](QString title) {
if (const auto strong = weak.get()) {
strong->toggleRecording(!recordStartDate, title);
strong->toggleRecording(
!recordStartDate,
title,
(*type) != RecordingType::AudioOnly,
(*type) == RecordingType::VideoPortrait);
}
};
if (recordStartDate) {
@@ -643,10 +568,14 @@ void FillMenu(
StopGroupCallRecordingBox,
done));
} else {
showBox(Box(
StartGroupCallRecordingBox,
real->title(),
done));
const auto typeDone = [=](RecordingType newType) {
*type = newType;
showBox(Box(
AddTitleGroupCallRecordingBox,
real->title(),
done));
};
showBox(Box(StartGroupCallRecordingBox, typeDone));
}
};
menu->addAction(MakeRecordingAction(

View File

@@ -1028,19 +1028,28 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
};
using namespace rpl::mappers;
const auto startedAsVideo = std::make_shared<bool>(real->recordVideo());
real->recordStartDateChanges(
) | rpl::map(
_1 != 0
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool recorded) {
const auto livestream = _call->peer()->isBroadcast();
const auto isVideo = real->recordVideo();
if (recorded) {
*startedAsVideo = isVideo;
}
validateRecordingMark(recorded);
showToast((recorded
? (livestream
? tr::lng_group_call_recording_started_channel
: isVideo
? tr::lng_group_call_recording_started_video
: tr::lng_group_call_recording_started)
: _call->recordingStoppedByMe()
? tr::lng_group_call_recording_saved
? ((*startedAsVideo)
? tr::lng_group_call_recording_saved_video
: tr::lng_group_call_recording_saved)
: (livestream
? tr::lng_group_call_recording_stopped_channel
: tr::lng_group_call_recording_stopped))(

View File

@@ -0,0 +1,376 @@
/*
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/ui/calls_group_recording_box.h"
#include "lang/lang_keys.h"
#include "ui/effects/animations.h"
#include "ui/image/image_prepare.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "styles/style_calls.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include <QSvgRenderer>
namespace Calls::Group {
namespace {
constexpr auto kRoundRadius = 9;
constexpr auto kMaxGroupCallLength = 40;
constexpr auto kSwitchDuration = 200;
constexpr auto kSelectDuration = 120;
class GraphicButton final : public Ui::AbstractButton {
public:
GraphicButton(
not_null<Ui::RpWidget*> parent,
const QString &filename,
int selectWidth = 0);
void setToggled(bool value);
protected:
void paintEvent(QPaintEvent *e);
private:
const style::margins _margins;
QSvgRenderer _renderer;
Ui::RoundRect _roundRect;
Ui::RoundRect _roundRectSelect;
Ui::Animations::Simple _animation;
bool _toggled = false;
};
class RecordingInfo final : public Ui::RpWidget {
public:
RecordingInfo(not_null<Ui::RpWidget*> parent);
void prepareAudio();
void prepareVideo();
RecordingType type() const;
private:
void setLabel(const QString &text);
const object_ptr<Ui::VerticalLayout> _container;
RecordingType _type = RecordingType::AudioOnly;
};
class Switcher final : public Ui::RpWidget {
public:
Switcher(
not_null<Ui::RpWidget*> parent,
rpl::producer<bool> &&toggled);
RecordingType type() const;
private:
const object_ptr<Ui::BoxContentDivider> _background;
const object_ptr<RecordingInfo> _audio;
const object_ptr<RecordingInfo> _video;
bool _toggled = false;
Ui::Animations::Simple _animation;
};
GraphicButton::GraphicButton(
not_null<Ui::RpWidget*> parent,
const QString &filename,
int selectWidth)
: AbstractButton(parent)
, _margins(selectWidth, selectWidth, selectWidth, selectWidth)
, _renderer(u":/gui/recording/%1.svg"_q.arg(filename))
, _roundRect(kRoundRadius, st::groupCallMembersBg)
, _roundRectSelect(kRoundRadius, st::groupCallActiveFg) {
const auto size = style::ConvertScale(_renderer.defaultSize());
resize((QRect(QPoint(), size) + _margins).size());
}
void GraphicButton::setToggled(bool value) {
if (_toggled == value) {
return;
}
_toggled = value;
_animation.start(
[=] { update(); },
_toggled ? 0. : 1.,
_toggled ? 1. : 0.,
kSelectDuration);
}
void GraphicButton::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto progress = _animation.value(_toggled ? 1. : 0.);
p.setOpacity(progress);
_roundRectSelect.paint(p, rect());
p.setOpacity(1.);
const auto r = rect() - _margins;
_roundRect.paint(p, r);
_renderer.render(&p, r);
}
RecordingInfo::RecordingInfo(not_null<Ui::RpWidget*> parent)
: RpWidget(parent)
, _container(this) {
sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
_container->resizeToWidth(size.width());
}, _container->lifetime());
}
void RecordingInfo::prepareAudio() {
_type = RecordingType::AudioOnly;
setLabel(tr::lng_group_call_recording_start_audio_subtitle(tr::now));
const auto wrap = _container->add(
object_ptr<Ui::RpWidget>(_container),
style::margins(0, st::groupCallRecordingAudioSkip, 0, 0));
const auto audioIcon = Ui::CreateChild<GraphicButton>(
wrap,
"info_audio");
wrap->resize(width(), audioIcon->height());
audioIcon->setAttribute(Qt::WA_TransparentForMouseEvents);
sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
audioIcon->moveToLeft((size.width() - audioIcon->width()) / 2, 0);
}, lifetime());
}
void RecordingInfo::prepareVideo() {
setLabel(tr::lng_group_call_recording_start_video_subtitle(tr::now));
const auto wrap = _container->add(
object_ptr<Ui::RpWidget>(_container),
style::margins());
const auto landscapeIcon = Ui::CreateChild<GraphicButton>(
wrap,
"info_video_landscape",
st::groupCallRecordingSelectWidth);
const auto portraitIcon = Ui::CreateChild<GraphicButton>(
wrap,
"info_video_portrait",
st::groupCallRecordingSelectWidth);
wrap->resize(width(), portraitIcon->height());
landscapeIcon->setToggled(true);
_type = RecordingType::VideoLandscape;
const auto icons = std::vector<GraphicButton*>{
landscapeIcon,
portraitIcon,
};
const auto types = std::map<GraphicButton*, RecordingType>{
{ landscapeIcon, RecordingType::VideoLandscape },
{ portraitIcon, RecordingType::VideoPortrait },
};
for (const auto icon : icons) {
icon->clicks(
) | rpl::start_with_next([=] {
for (const auto &i : icons) {
i->setToggled(icon == i);
}
_type = types.at(icon);
}, lifetime());
}
wrap->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
const auto wHalf = size.width() / icons.size();
for (auto i = 0; i < icons.size(); i++) {
const auto &icon = icons[i];
icon->moveToLeft(
wHalf * i + (wHalf - icon->width()) / 2,
(size.height() - icon->height()) / 2);
}
}, lifetime());
}
void RecordingInfo::setLabel(const QString &text) {
const auto label = _container->add(
object_ptr<Ui::FlatLabel>(
_container,
text,
st::groupCallRecordingSubLabel),
st::groupCallRecordingSubLabelMargins);
rpl::combine(
sizeValue(),
label->sizeValue()
) | rpl::start_with_next([=](QSize my, QSize labelSize) {
label->moveToLeft(
(my.width() - labelSize.width()) / 2,
label->y(),
my.width());
}, label->lifetime());
}
RecordingType RecordingInfo::type() const {
return _type;
}
Switcher::Switcher(
not_null<Ui::RpWidget*> parent,
rpl::producer<bool> &&toggled)
: RpWidget(parent)
, _background(this, st::groupCallRecordingInfoHeight, st::groupCallBg)
, _audio(this)
, _video(this) {
_audio->prepareAudio();
_video->prepareVideo();
resize(0, st::groupCallRecordingInfoHeight);
const auto updatePositions = [=](float64 progress) {
_audio->moveToLeft(-width() * progress, 0);
_video->moveToLeft(_audio->x() + _audio->width(), 0);
};
sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
_audio->resize(size.width(), size.height());
_video->resize(size.width(), size.height());
updatePositions(_toggled ? 1. : 0.);
_background->lower();
_background->setGeometry(QRect(QPoint(), size));
}, lifetime());
std::move(
toggled
) | rpl::start_with_next([=](bool toggled) {
_toggled = toggled;
_animation.start(
updatePositions,
toggled ? 0. : 1.,
toggled ? 1. : 0.,
kSwitchDuration);
}, lifetime());
}
RecordingType Switcher::type() const {
return _toggled ? _video->type() : _audio->type();
}
} // namespace
void EditGroupCallTitleBox(
not_null<Ui::GenericBox*> box,
const QString &placeholder,
const QString &title,
bool livestream,
Fn<void(QString)> done) {
box->setTitle(livestream
? tr::lng_group_call_edit_title_channel()
: tr::lng_group_call_edit_title());
const auto input = box->addRow(object_ptr<Ui::InputField>(
box,
st::groupCallField,
rpl::single(placeholder),
title));
input->setMaxLength(kMaxGroupCallLength);
box->setFocusCallback([=] {
input->setFocusFast();
});
const auto submit = [=] {
const auto result = input->getLastText().trimmed();
box->closeBox();
done(result);
};
QObject::connect(input, &Ui::InputField::submitted, submit);
box->addButton(tr::lng_settings_save(), submit);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
void StartGroupCallRecordingBox(
not_null<Ui::GenericBox*> box,
Fn<void(RecordingType)> done) {
box->setTitle(tr::lng_group_call_recording_start());
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_group_call_recording_start_sure(),
st::groupCallBoxLabel));
const auto checkbox = box->addRow(
object_ptr<Ui::Checkbox>(
box,
tr::lng_group_call_recording_start_checkbox(),
false,
st::groupCallCheckbox),
style::margins(
st::boxRowPadding.left(),
st::boxRowPadding.left(),
st::boxRowPadding.right(),
st::boxRowPadding.bottom()));
const auto switcher = box->addRow(
object_ptr<Switcher>(box, checkbox->checkedChanges()),
st::groupCallRecordingInfoMargins);
box->addButton(tr::lng_continue(), [=] {
const auto type = switcher->type();
box->closeBox();
done(type);
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
void AddTitleGroupCallRecordingBox(
not_null<Ui::GenericBox*> box,
const QString &title,
Fn<void(QString)> done) {
box->setTitle(tr::lng_group_call_recording_start_title());
const auto input = box->addRow(object_ptr<Ui::InputField>(
box,
st::groupCallField,
tr::lng_group_call_recording_start_field(),
title));
box->setFocusCallback([=] {
input->setFocusFast();
});
const auto submit = [=] {
const auto result = input->getLastText().trimmed();
box->closeBox();
done(result);
};
QObject::connect(input, &Ui::InputField::submitted, submit);
box->addButton(tr::lng_group_call_recording_start_button(), submit);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
void StopGroupCallRecordingBox(
not_null<Ui::GenericBox*> box,
Fn<void(QString)> done) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_group_call_recording_stop_sure(),
st::groupCallBoxLabel),
style::margins(
st::boxRowPadding.left(),
st::boxPadding.top(),
st::boxRowPadding.right(),
st::boxPadding.bottom()));
box->addButton(tr::lng_box_ok(), [=] {
box->closeBox();
done(QString());
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
} // namespace Calls::Group

View File

@@ -0,0 +1,42 @@
/*
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 Ui {
class GenericBox;
} // namespace Ui
namespace Calls::Group {
enum class RecordingType {
AudioOnly,
VideoLandscape,
VideoPortrait,
};
void EditGroupCallTitleBox(
not_null<Ui::GenericBox*> box,
const QString &placeholder,
const QString &title,
bool livestream,
Fn<void(QString)> done);
void StartGroupCallRecordingBox(
not_null<Ui::GenericBox*> box,
Fn<void(RecordingType)> done);
void AddTitleGroupCallRecordingBox(
not_null<Ui::GenericBox*> box,
const QString &title,
Fn<void(QString)> done);
void StopGroupCallRecordingBox(
not_null<Ui::GenericBox*> box,
Fn<void(QString)> done);
} // namespace Calls::Group

View File

@@ -389,7 +389,10 @@ void GroupCall::applyCallFields(const MTPDgroupCall &data) {
setServerParticipantsCount(data.vparticipants_count().v);
changePeerEmptyCallFlag();
_title = qs(data.vtitle().value_or_empty());
_recordStartDate = data.vrecord_start_date().value_or_empty();
{
_recordVideo = data.is_record_video_active();
_recordStartDate = data.vrecord_start_date().value_or_empty();
}
_scheduleDate = data.vschedule_date().value_or_empty();
_scheduleStartSubscribed = data.is_schedule_start_subscribed();
_unmutedVideoLimit = data.vunmuted_video_limit().v;

View File

@@ -98,6 +98,9 @@ public:
[[nodiscard]] int unmutedVideoLimit() const {
return _unmutedVideoLimit.current();
}
[[nodiscard]] bool recordVideo() const {
return _recordVideo.current();
}
void setPeer(not_null<PeerData*> peer);
@@ -214,6 +217,7 @@ private:
int _serverParticipantsCount = 0;
rpl::variable<int> _fullCount = 0;
rpl::variable<int> _unmutedVideoLimit = 0;
rpl::variable<bool> _recordVideo = 0;
rpl::variable<TimeId> _recordStartDate = 0;
rpl::variable<TimeId> _scheduleDate = 0;
rpl::variable<bool> _scheduleStartSubscribed = false;