diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 295bcd35c1..0c20a1913f 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -505,6 +505,27 @@ groupCallMultiSelect: MultiSelect(defaultMultiSelect) { } } +groupCallField: InputField(defaultInputField) { + textMargins: margins(2px, 7px, 2px, 0px); + + textBg: transparent; + textFg: groupCallMembersFg; + + placeholderFg: groupCallMemberNotJoinedStatus; + placeholderFgActive: groupCallMemberNotJoinedStatus; + placeholderFgError: groupCallMemberNotJoinedStatus; + placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderScale: 0.; + placeholderFont: normalFont; + heightMin: 32px; + + borderFg: inputBorderFg; + borderFgActive: groupCallMemberInactiveStatus; + borderFgError: activeLineFgError; + + menu: groupCallPopupMenu; +} + groupCallMembersTop: 62px; groupCallTitleTop: 14px; groupCallSubtitleTop: 33px; diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index 2aa99e0e17..0d9bde1c3b 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -705,6 +705,22 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) { } } +void GroupCall::changeTitle(const QString &title) { + const auto real = _peer->groupCall(); + if (!real || real->id() != _id || real->title() == title) { + return; + } + + real->setTitle(title); + _api.request(MTPphone_EditGroupCallTitle( + inputCall(), + MTP_string(title) + )).done([=](const MTPUpdates &result) { + _peer->session().api().applyUpdates(result); + }).fail([=](const RPCError &error) { + }).send(); +} + void GroupCall::createAndStartController() { const auto &settings = Core::App().settings(); diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index c681222c23..2ed5f8c325 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -115,6 +115,7 @@ public: void join(const MTPInputGroupCall &inputCall); void handleUpdate(const MTPGroupCall &call); void handleUpdate(const MTPDupdateGroupCallParticipants &data); + void changeTitle(const QString &title); void setMuted(MuteState mute); [[nodiscard]] MuteState muted() const { diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index 5c404cbbb7..4c9077ba2e 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_group_call.h" #include "data/data_session.h" +#include "data/data_changes.h" #include "main/main_session.h" #include "base/event_filter.h" #include "boxes/peers/edit_participants_box.h" @@ -324,6 +325,7 @@ GroupPanel::GroupPanel(not_null call) _peer, _window->lifetime(), [=](not_null channel) { migrate(channel); }); + setupRealCallViewers(call); initWindow(); initWidget(); @@ -334,6 +336,22 @@ GroupPanel::GroupPanel(not_null call) GroupPanel::~GroupPanel() = default; +void GroupPanel::setupRealCallViewers(not_null call) { + const auto peer = call->peer(); + peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::GroupCall + ) | rpl::map([=] { + return peer->groupCall(); + }) | rpl::filter([=](Data::GroupCall *real) { + return _call && real && (real->id() == _call->id()); + }) | rpl::take( + 1 + ) | rpl::start_with_next([=](not_null real) { + subscribeToChanges(real); + }, _window->lifetime()); +} + bool GroupPanel::isActive() const { return _window->isActiveWindow() && _window->isVisible() @@ -565,6 +583,10 @@ void GroupPanel::initWithCall(GroupCall *call) { }, _callLifetime); } +void GroupPanel::subscribeToChanges(not_null real) { + _titleText = real->titleValue(); +} + void GroupPanel::addMembers() { const auto real = _peer->groupCall(); if (!_call || !real || real->id() != _call->id()) { @@ -829,32 +851,26 @@ void GroupPanel::updateControlsGeometry() { } void GroupPanel::refreshTitle() { - if (const auto titleRect = computeTitleRect()) { + 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(), - Info::Profile::NameValue(_peer), + rpl::duplicate(text), st::groupCallTitleLabel); _title->show(); _title->setAttribute(Qt::WA_TransparentForMouseEvents); } - 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()) { - _title->resizeToWidth(best); - _title->moveToLeft(from, top); - } 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); - } + refreshTitleGeometry(); } else if (_title) { _title.destroy(); } @@ -879,6 +895,30 @@ void GroupPanel::refreshTitle() { top); } +void GroupPanel::refreshTitleGeometry() { + const auto titleRect = computeTitleRect(); + if (!_title || !titleRect) { + return; + } + 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()) { + _title->resizeToWidth(best); + _title->moveToLeft(from, top); + } 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); + } +} + void GroupPanel::paint(QRect clip) { Painter p(widget()); diff --git a/Telegram/SourceFiles/calls/calls_group_panel.h b/Telegram/SourceFiles/calls/calls_group_panel.h index 1887f3dbb0..02aa5d4249 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/calls_group_panel.h @@ -19,6 +19,7 @@ class Image; namespace Data { class PhotoMedia; class CloudImageView; +class GroupCall; } // namespace Data namespace Ui { @@ -100,6 +101,9 @@ private: [[nodiscard]] int computeMembersListTop() const; [[nodiscard]] std::optional computeTitleRect() const; void refreshTitle(); + void refreshTitleGeometry(); + void setupRealCallViewers(not_null call); + void subscribeToChanges(not_null real); void migrate(not_null channel); void subscribeToPeerChanges(); @@ -119,6 +123,7 @@ private: object_ptr _title = { nullptr }; object_ptr _subtitle = { nullptr }; object_ptr _members; + rpl::variable _titleText; object_ptr _settings; std::unique_ptr _mute; diff --git a/Telegram/SourceFiles/calls/calls_group_settings.cpp b/Telegram/SourceFiles/calls/calls_group_settings.cpp index 153ad5688f..16858fa535 100644 --- a/Telegram/SourceFiles/calls/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/calls_group_settings.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/level_meter.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "ui/wrap/slide_wrap.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" @@ -84,6 +85,28 @@ void SaveCallJoinMuted( QString::number(delay / 1000., 'f', 2)); } +void EditGroupCallTitleBox( + not_null box, + const QString &placeholder, + const QString &title, + Fn done) { + box->setTitle(tr::lng_group_call_edit_title()); + const auto input = box->addRow(object_ptr( + box, + st::groupCallField, + rpl::single(placeholder), + title)); + box->setFocusCallback([=] { + input->setFocusFast(); + }); + box->addButton(tr::lng_settings_save(), [=] { + const auto result = input->getLastText(); + box->closeBox(); + done(result); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + } // namespace void GroupCallSettingsBox( @@ -119,6 +142,7 @@ void GroupCallSettingsBox( const auto canChangeJoinMuted = (goodReal && real->canChangeJoinMuted()); const auto addCheck = (peer->canManageGroupCall() && canChangeJoinMuted); const auto addEditJoinAs = (call->possibleJoinAs().size() > 1); + const auto addEditTitle = peer->canManageGroupCall(); if (addCheck || addEditJoinAs) { AddSkip(layout); } @@ -128,6 +152,12 @@ void GroupCallSettingsBox( tr::lng_group_call_display_as_header(), st::groupCallSettingsButton).get() : nullptr; + const auto editTitle = (goodReal && addEditTitle) + ? AddButton( + layout, + tr::lng_group_call_edit_title(), + st::groupCallSettingsButton).get() + : nullptr; if (editJoinAs) { editJoinAs->setClickedCallback([=] { const auto context = Group::ChooseJoinAsProcess::Context::Switch; @@ -145,6 +175,18 @@ void GroupCallSettingsBox( call->joinAs()); }); } + if (editTitle) { + editTitle->setClickedCallback([=] { + const auto done = [=](const QString &title) { + call->changeTitle(title); + }; + box->getDelegate()->show(Box( + EditGroupCallTitleBox, + peer->name, + goodReal ? real->title() : QString(), + 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 79f44003fb..7480bbdae3 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -175,10 +175,12 @@ void GroupCall::applyUpdate(const MTPGroupCall &update) { void GroupCall::applyCall(const MTPGroupCall &call, bool force) { call.match([&](const MTPDgroupCall &data) { + const auto title = qs(data.vtitle().value_or_empty()); const auto changed = (_version != data.vversion().v) || (_fullCount.current() != data.vparticipants_count().v) || (_joinMuted != data.is_join_muted()) - || (_canChangeJoinMuted != data.is_can_change_join_muted()); + || (_canChangeJoinMuted != data.is_can_change_join_muted()) + || (_title.current() != title); if (!force && !changed) { return; } else if (!force && _version > data.vversion().v) { @@ -189,6 +191,7 @@ void GroupCall::applyCall(const MTPGroupCall &call, bool force) { _canChangeJoinMuted = data.is_can_change_join_muted(); _version = data.vversion().v; _fullCount = data.vparticipants_count().v; + _title = title; 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 c645e28911..f353e4c0e4 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -28,6 +28,15 @@ public: [[nodiscard]] uint64 id() const; [[nodiscard]] not_null peer() const; [[nodiscard]] MTPInputGroupCall input() const; + [[nodiscard]] QString title() const { + return _title.current(); + } + [[nodiscard]] rpl::producer titleValue() const { + return _title.value(); + } + void setTitle(const QString &title) { + _title = title; + } void setPeer(not_null peer); @@ -105,6 +114,7 @@ private: int _version = 0; mtpRequestId _participantsRequestId = 0; mtpRequestId _reloadRequestId = 0; + rpl::variable _title; std::vector _participants; base::flat_map> _participantPeerBySsrc;