From 7edc91e29e1448802a2417e4e70b82fbca83adff Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 9 Mar 2021 17:24:32 +0400 Subject: [PATCH] Show recording indicator. --- Telegram/Resources/langs/lang.strings | 2 +- Telegram/SourceFiles/calls/calls.style | 3 + .../SourceFiles/calls/calls_group_panel.cpp | 132 ++++++++++++------ .../SourceFiles/calls/calls_group_panel.h | 5 +- .../calls/calls_group_settings.cpp | 72 +++++----- Telegram/SourceFiles/data/data_group_call.cpp | 2 +- Telegram/SourceFiles/data/data_group_call.h | 10 +- 7 files changed, 146 insertions(+), 80 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 25aff93f4..61fddf8f2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2004,7 +2004,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "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_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}**."; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 931b47c97..9d9eb7445 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -560,6 +560,9 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) { } groupCallAddButtonPosition: point(10px, 7px); groupCallMembersWidthMax: 360px; +groupCallRecordingMark: 6px; +groupCallRecordingMarkSkip: 4px; +groupCallRecordingMarkTop: 6px; groupCallActiveButton: IconButton { width: 36px; diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index 9ec74ab8a..d9bb38732 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -427,7 +427,7 @@ void GroupPanel::initWindow() { 0, 0, widget()->width(), - computeMembersListTop()); + st::groupCallMembersTop); return titleRect.contains(widgetPoint) ? (Flag::Move | Flag::Maximize) : Flag::None; @@ -593,6 +593,46 @@ void GroupPanel::initWithCall(GroupCall *call) { void GroupPanel::subscribeToChanges(not_null real) { _titleText = real->titleValue(); + + const auto validateRecordingMark = [=](bool recording) { + if (!recording && _recordingMark) { + _recordingMark.destroy(); + } else if (recording && !_recordingMark) { + _recordingMark.create(widget()); + const auto size = st::groupCallRecordingMark; + const auto skip = st::groupCallRecordingMarkSkip; + _recordingMark->resize(size + 2 * skip, size + 2 * skip); + _recordingMark->setClickedCallback([=] { + Ui::Toast::Show( + widget(), + tr::lng_group_call_is_recorded(tr::now)); + }); + _recordingMark->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(_recordingMark.data()); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::groupCallMemberMutedIcon); + p.drawEllipse(skip, skip, size, size); + }, _recordingMark->lifetime()); + } + refreshTitleGeometry(); + }; + + using namespace rpl::mappers; + real->recordStartDateChanges( + ) | rpl::map( + _1 != 0 + ) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](bool recorded) { + validateRecordingMark(recorded); + Ui::Toast::Show( + widget(), + (recorded + ? tr::lng_group_call_recording_started(tr::now) + : tr::lng_group_call_recording_stopped(tr::now))); + }, _callLifetime); + validateRecordingMark(real->recordStartDate() != 0); } void GroupPanel::addMembers() { @@ -805,20 +845,17 @@ void GroupPanel::initGeometry() { updateControlsGeometry(); } -int GroupPanel::computeMembersListTop() const { - if (computeTitleRect().has_value()) { - return st::groupCallMembersTop; - } - return st::groupCallMembersTop - - (st::groupCallSubtitleTop - st::groupCallTitleTop); -} - -std::optional GroupPanel::computeTitleRect() const { +QRect GroupPanel::computeTitleRect() const { + const auto skip = st::groupCallTitleTop; + const auto width = widget()->width(); #ifdef Q_OS_MAC - return QRect(70, 0, widget()->width() - 70, 28); + return QRect(70, 0, width - skip - 70, 28); #else // Q_OS_MAC const auto controls = _controls->geometry(); - return QRect(0, 0, controls.x(), controls.height()); + const auto right = controls.x() + controls.width() + skip; + return (controls.center().x() < width / 2) + ? QRect(right, 0, width - right - skip, controls.height()) + : QRect(skip, 0, controls.x() - 2 * skip, controls.height()); #endif // !Q_OS_MAC } @@ -839,7 +876,7 @@ void GroupPanel::updateControlsGeometry() { st::groupCallMembersWidthMax); const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip; const auto buttonsTop = widget()->height() - st::groupCallButtonBottomSkip; - const auto membersTop = computeMembersListTop(); + const auto membersTop = st::groupCallMembersTop; const auto availableHeight = muteTop - membersTop - st::groupCallMembersMargin.bottom(); @@ -859,29 +896,25 @@ void GroupPanel::updateControlsGeometry() { } void GroupPanel::refreshTitle() { - if (computeTitleRect().has_value()) { - if (!_title) { - auto text = rpl::combine( - Info::Profile::NameValue(_peer), - _titleText.value() - ) | rpl::map([=]( - const TextWithEntities &name, - const QString &title) { - return title.isEmpty() ? name.text : title; - }) | rpl::after_next([=] { - refreshTitleGeometry(); - }); - _title.create( - widget(), - rpl::duplicate(text), - st::groupCallTitleLabel); - _title->show(); - _title->setAttribute(Qt::WA_TransparentForMouseEvents); - } - refreshTitleGeometry(); - } else if (_title) { - _title.destroy(); + if (!_title) { + auto text = rpl::combine( + Info::Profile::NameValue(_peer), + _titleText.value() + ) | rpl::map([=]( + const TextWithEntities &name, + const QString &title) { + return title.isEmpty() ? name.text : title; + }) | rpl::after_next([=] { + refreshTitleGeometry(); + }); + _title.create( + widget(), + rpl::duplicate(text), + st::groupCallTitleLabel); + _title->show(); + _title->setAttribute(Qt::WA_TransparentForMouseEvents); } + refreshTitleGeometry(); if (!_subtitle) { _subtitle.create( widget(), @@ -904,26 +937,41 @@ void GroupPanel::refreshTitle() { } void GroupPanel::refreshTitleGeometry() { - const auto titleRect = computeTitleRect(); - if (!_title || !titleRect) { + if (!_title) { return; } + const auto fullRect = computeTitleRect(); + const auto recordingWidth = 2 * st::groupCallRecordingMarkSkip + + st::groupCallRecordingMark; + const auto titleRect = _recordingMark + ? QRect( + fullRect.x(), + fullRect.y(), + fullRect.width() - _recordingMark->width(), + fullRect.height()) + : fullRect; const auto best = _title->naturalWidth(); const auto from = (widget()->width() - best) / 2; const auto top = st::groupCallTitleTop; - const auto left = titleRect->x(); - if (from >= left && from + best <= left + titleRect->width()) { + const auto left = titleRect.x(); + if (from >= left && from + best <= left + titleRect.width()) { _title->resizeToWidth(best); _title->moveToLeft(from, top); - } else if (titleRect->width() < best) { - _title->resizeToWidth(titleRect->width()); + } else if (titleRect.width() < best) { + _title->resizeToWidth(titleRect.width()); _title->moveToLeft(left, top); } else if (from < left) { _title->resizeToWidth(best); _title->moveToLeft(left, top); } else { _title->resizeToWidth(best); - _title->moveToLeft(left + titleRect->width() - best, top); + _title->moveToLeft(left + titleRect.width() - best, top); + } + if (_recordingMark) { + const auto markTop = top + st::groupCallRecordingMarkTop; + _recordingMark->move( + _title->x() + _title->width(), + markTop - st::groupCallRecordingMarkSkip); } } diff --git a/Telegram/SourceFiles/calls/calls_group_panel.h b/Telegram/SourceFiles/calls/calls_group_panel.h index 02aa5d424..6a144d5cb 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/calls_group_panel.h @@ -23,6 +23,7 @@ class GroupCall; } // namespace Data namespace Ui { +class AbstractButton; class CallButton; class CallMuteButton; class IconButton; @@ -98,8 +99,7 @@ private: void addMembers(); void kickMember(not_null user); void kickMemberSure(not_null user); - [[nodiscard]] int computeMembersListTop() const; - [[nodiscard]] std::optional computeTitleRect() const; + [[nodiscard]] QRect computeTitleRect() const; void refreshTitle(); void refreshTitleGeometry(); void setupRealCallViewers(not_null call); @@ -122,6 +122,7 @@ private: object_ptr _title = { nullptr }; object_ptr _subtitle = { nullptr }; + object_ptr _recordingMark = { nullptr }; object_ptr _members; rpl::variable _titleText; diff --git a/Telegram/SourceFiles/calls/calls_group_settings.cpp b/Telegram/SourceFiles/calls/calls_group_settings.cpp index 5d50ed9e4..925e5fb18 100644 --- a/Telegram/SourceFiles/calls/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/calls_group_settings.cpp @@ -213,37 +213,45 @@ void GroupCallSettingsBox( tr::lng_group_call_edit_title(), st::groupCallSettingsButton).get() : nullptr; - const auto recordStartDate = goodReal ? real->recordStartDate() : 0; - auto recordingLabel = [&] { - return ; + static const auto ToDurationFrom = [](TimeId startDate) { + return [=] { + const auto now = base::unixtime::now(); + const auto elapsed = std::max(now - startDate, 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')); + }; }; + static const auto ToRecordDuration = [](TimeId startDate) { + return !startDate + ? (rpl::single(QString()) | rpl::type_erased()) + : rpl::single( + rpl::empty_value() + ) | rpl::then(base::timer_each( + crl::time(1000) + )) | rpl::map(ToDurationFrom(startDate)); + }; + using namespace rpl::mappers; 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')); - }), + rpl::conditional( + real->recordStartDateValue() | rpl::map(!!_1), + tr::lng_group_call_recording_stop(), + tr::lng_group_call_recording_start()), + real->recordStartDateValue( + ) | rpl::map( + ToRecordDuration + ) | rpl::flatten_latest(), st::groupCallSettingsButton).get(); if (editJoinAs) { editJoinAs->setClickedCallback([=] { @@ -282,14 +290,14 @@ void GroupCallSettingsBox( } if (editRecording) { editRecording->setClickedCallback([=] { + const auto real = peer->groupCall(); + const auto id = call->id(); + if (!real || real->id() != id) { + return; + } + const auto recordStartDate = real->recordStartDate(); 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) { diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 7868ec681..18fdd3d0f 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -184,7 +184,7 @@ void GroupCall::applyCall(const MTPGroupCall &call, bool force) { || (_fullCount.current() != data.vparticipants_count().v) || (_canChangeJoinMuted != data.is_can_change_join_muted()) || (_title.current() != title) - || (_recordStartDate != recordDate); + || (_recordStartDate.current() != recordDate); if (!force && !changed) { return; } else if (!force && _version > data.vversion().v) { diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 5f08a8db4..872b7dac3 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -54,7 +54,13 @@ public: _title = title; } [[nodiscard]] TimeId recordStartDate() const { - return _recordStartDate; + return _recordStartDate.current(); + } + [[nodiscard]] rpl::producer recordStartDateValue() const { + return _recordStartDate.value(); + } + [[nodiscard]] rpl::producer recordStartDateChanges() const { + return _recordStartDate.changes(); } void setPeer(not_null peer); @@ -130,7 +136,7 @@ private: base::Timer _speakingByActiveFinishTimer; QString _nextOffset; rpl::variable _fullCount = 0; - TimeId _recordStartDate = 0; + rpl::variable _recordStartDate = 0; base::flat_map _unknownSpokenSsrcs; base::flat_map _unknownSpokenPeerIds;