diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4ea4e7931..25aff93f4 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2000,6 +2000,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_recording_stop" = "Stop recording"; "lng_group_call_recording_started" = "Voice chat recording started."; "lng_group_call_recording_stopped" = "Voice chat recording stopped."; +"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"; +"lng_group_call_recording_start_button" = "Start"; +"lng_group_call_is_recorded" = "Voice chat is being recorded"; +"lng_group_call_can_speak_here" = "You can now speak."; +"lng_group_call_can_speak" = "You can now speak in **{chat}**."; "lng_no_mic_permission" = "Telegram needs access to your microphone so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index 1d6803873..c471e9060 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -876,6 +876,29 @@ void GroupCall::changeTitle(const QString &title) { }).send(); } +void GroupCall::toggleRecording(bool enabled, const QString &title) { + const auto real = _peer->groupCall(); + if (!real || real->id() != _id) { + return; + } + + const auto already = (real->recordStartDate() != 0); + if (already == enabled) { + return; + } + + using Flag = MTPphone_ToggleGroupCallRecord::Flag; + _api.request(MTPphone_ToggleGroupCallRecord( + MTP_flags((enabled ? Flag::f_start : Flag(0)) + | (title.isEmpty() ? Flag(0) : Flag::f_title)), + inputCall(), + MTP_string(title) + )).done([=](const MTPUpdates &result) { + _peer->session().api().applyUpdates(result); + }).fail([=](const RPCError &error) { + }).send(); +} + void GroupCall::ensureControllerCreated() { if (_instance) { return; diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index f11a618f5..5141f77b4 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -119,6 +119,7 @@ public: void handleUpdate(const MTPGroupCall &call); void handleUpdate(const MTPDupdateGroupCallParticipants &data); void changeTitle(const QString &title); + void toggleRecording(bool enabled, const QString &title); void setMuted(MuteState mute); void setMutedAndUpdate(MuteState mute); diff --git a/Telegram/SourceFiles/calls/calls_group_settings.cpp b/Telegram/SourceFiles/calls/calls_group_settings.cpp index 21168ce00..5d50ed9e4 100644 --- a/Telegram/SourceFiles/calls/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/calls_group_settings.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "base/global_shortcuts.h" #include "base/platform/base_platform_info.h" +#include "base/unixtime.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_group_call.h" @@ -100,13 +101,66 @@ void EditGroupCallTitleBox( input->setFocusFast(); }); box->addButton(tr::lng_settings_save(), [=] { - const auto result = input->getLastText(); + const auto result = input->getLastText().trimmed(); box->closeBox(); done(result); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } +void StartGroupCallRecordingBox( + not_null box, + const QString &title, + Fn done) { + box->setTitle(tr::lng_group_call_recording_start()); + + box->addRow( + object_ptr( + box.get(), + tr::lng_group_call_recording_start_sure(), + st::groupCallBoxLabel)); + + const auto input = box->addRow(object_ptr( + box, + st::groupCallField, + tr::lng_group_call_recording_start_field(), + title)); + box->setFocusCallback([=] { + input->setFocusFast(); + }); + box->addButton(tr::lng_group_call_recording_start_button(), [=] { + const auto result = input->getLastText().trimmed(); + if (result.isEmpty()) { + input->showError(); + return; + } + box->closeBox(); + done(result); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + +void StopGroupCallRecordingBox( + not_null box, + Fn done) { + box->addRow( + object_ptr( + 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 void GroupCallSettingsBox( @@ -141,8 +195,9 @@ void GroupCallSettingsBox( const auto joinMuted = goodReal ? real->joinMuted() : false; const auto canChangeJoinMuted = (goodReal && real->canChangeJoinMuted()); const auto addCheck = (peer->canManageGroupCall() && canChangeJoinMuted); - const auto addEditJoinAs = (call->possibleJoinAs().size() > 1); - const auto addEditTitle = peer->canManageGroupCall(); + const auto addEditJoinAs = (call->possibleJoinAs().size() > 1); // #TODO calls when to show + const auto addEditTitle = peer->canManageGroupCall() && goodReal; + const auto addEditRecording = peer->canManageGroupCall() && goodReal; if (addCheck || addEditJoinAs) { AddSkip(layout); } @@ -152,12 +207,44 @@ void GroupCallSettingsBox( tr::lng_group_call_display_as_header(), st::groupCallSettingsButton).get() : nullptr; - const auto editTitle = (goodReal && addEditTitle) + const auto editTitle = addEditTitle ? AddButton( layout, tr::lng_group_call_edit_title(), st::groupCallSettingsButton).get() : nullptr; + const auto recordStartDate = goodReal ? real->recordStartDate() : 0; + auto recordingLabel = [&] { + return ; + }; + const auto editRecording = !addEditRecording + ? nullptr + : !recordStartDate + ? AddButton( + layout, + tr::lng_group_call_recording_start(), + st::groupCallSettingsButton).get() + : AddButtonWithLabel( + layout, + tr::lng_group_call_recording_stop(), + base::timer_each( + crl::time(1000) + ) | rpl::map([=] { + const auto now = base::unixtime::now(); + const auto elapsed = std::max(now - recordStartDate, 0); + const auto hours = elapsed / 3600; + const auto minutes = (elapsed % 3600) / 60; + const auto seconds = (elapsed % 60); + return hours + ? QString("%1:%2:%3" + ).arg(hours + ).arg(minutes, 2, 10, QChar('0') + ).arg(seconds, 2, 10, QChar('0')) + : QString("%1:%2" + ).arg(minutes + ).arg(seconds, 2, 10, QChar('0')); + }), + st::groupCallSettingsButton).get(); if (editJoinAs) { editJoinAs->setClickedCallback([=] { const auto context = Group::ChooseJoinAsProcess::Context::Switch; @@ -184,14 +271,39 @@ void GroupCallSettingsBox( editTitle->setClickedCallback([=] { const auto done = [=](const QString &title) { call->changeTitle(title); + box->closeBox(); }; box->getDelegate()->show(Box( EditGroupCallTitleBox, peer->name, - goodReal ? real->title() : QString(), + real->title(), done)); }); } + if (editRecording) { + editRecording->setClickedCallback([=] { + const auto done = [=](QString title) { + call->toggleRecording(!recordStartDate, title); + const auto container = box->getDelegate()->outerContainer(); + Ui::Toast::Show( + container, + (recordStartDate + ? tr::lng_group_call_recording_stopped(tr::now) + : tr::lng_group_call_recording_started(tr::now))); + box->closeBox(); + }; + if (recordStartDate) { + box->getDelegate()->show(Box( + StopGroupCallRecordingBox, + done)); + } else { + box->getDelegate()->show(Box( + StartGroupCallRecordingBox, + real->title(), + done)); + } + }); + } const auto muteJoined = addCheck ? AddButton( layout, diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 2c4e272a4..7868ec681 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -179,10 +179,12 @@ void GroupCall::applyCall(const MTPGroupCall &call, bool force) { _version = data.vversion().v; } const auto title = qs(data.vtitle().value_or_empty()); + const auto recordDate = data.vrecord_start_date().value_or_empty(); const auto changed = (_joinMuted != data.is_join_muted()) || (_fullCount.current() != data.vparticipants_count().v) || (_canChangeJoinMuted != data.is_can_change_join_muted()) - || (_title.current() != title); + || (_title.current() != title) + || (_recordStartDate != recordDate); if (!force && !changed) { return; } else if (!force && _version > data.vversion().v) { @@ -193,6 +195,7 @@ void GroupCall::applyCall(const MTPGroupCall &call, bool force) { _canChangeJoinMuted = data.is_can_change_join_muted(); _fullCount = data.vparticipants_count().v; _title = title; + _recordStartDate = recordDate; changePeerEmptyCallFlag(); }, [&](const MTPDgroupCallDiscarded &data) { const auto id = _id; diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 9cb779dc4..5f08a8db4 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -53,6 +53,9 @@ public: void setTitle(const QString &title) { _title = title; } + [[nodiscard]] TimeId recordStartDate() const { + return _recordStartDate; + } void setPeer(not_null peer); @@ -127,6 +130,7 @@ private: base::Timer _speakingByActiveFinishTimer; QString _nextOffset; rpl::variable _fullCount = 0; + TimeId _recordStartDate = 0; base::flat_map _unknownSpokenSsrcs; base::flat_map _unknownSpokenPeerIds;