From f8783c3bfc95e4c9ff745c476921204318e11552 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 9 Mar 2021 19:13:48 +0400 Subject: [PATCH] Move actions from Settings to Three-Dot-Menu. --- Telegram/SourceFiles/calls/calls.style | 41 ++- .../SourceFiles/calls/calls_group_panel.cpp | 312 +++++++++++++++++- .../SourceFiles/calls/calls_group_panel.h | 8 + .../calls/calls_group_settings.cpp | 194 +---------- Telegram/SourceFiles/ui/special_buttons.h | 4 +- 5 files changed, 358 insertions(+), 201 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 9d9eb7445..dd10feece 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -428,6 +428,17 @@ groupCallPopupMenu: PopupMenu(defaultPopupMenu) { animation: groupCallPanelAnimation; } +groupCallInnerDropdown: InnerDropdown(defaultInnerDropdown) { + shadow: groupCallMenuShadow; + animation: groupCallPanelAnimation; + bg: groupCallMenuBg; + scroll: defaultSolidScroll; + scrollPadding: margins(0px, 8px, 0px, 8px); +} +groupCallDropdownMenu: DropdownMenu(defaultDropdownMenu) { + wrap: groupCallInnerDropdown; + menu: groupCallMenu; +} groupCallMembersListItem: PeerListItem(defaultPeerListItem) { button: OutlineButton(defaultPeerListButton) { textBg: groupCallMembersBg; @@ -526,9 +537,9 @@ groupCallField: InputField(defaultInputField) { menu: groupCallPopupMenu; } -groupCallMembersTop: 62px; -groupCallTitleTop: 14px; -groupCallSubtitleTop: 33px; +groupCallMembersTop: 51px; +groupCallTitleTop: 8px; +groupCallSubtitleTop: 26px; groupCallMembersMargin: margins(16px, 16px, 16px, 28px); groupCallAddMember: SettingsButton(defaultSettingsButton) { @@ -562,7 +573,29 @@ groupCallAddButtonPosition: point(10px, 7px); groupCallMembersWidthMax: 360px; groupCallRecordingMark: 6px; groupCallRecordingMarkSkip: 4px; -groupCallRecordingMarkTop: 6px; +groupCallRecordingMarkTop: 8px; + +groupCallMenuTogglePosition: point(13px, 8px); +groupCallMenuToggle: IconButton { + width: 36px; + height: 36px; + + icon: icon {{ "info/edit/dotsmini", groupCallMemberInactiveIcon }}; + iconOver: icon {{ "info/edit/dotsmini", groupCallMemberInactiveIcon }}; + iconPosition: point(6px, 6px); + + rippleAreaPosition: point(3px, 3px); + rippleAreaSize: 30px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: groupCallMembersBg; + } +} +groupCallJoinAsToggle: UserpicButton(defaultUserpicButton) { + size: size(36px, 36px); + photoSize: 30px; + photoPosition: point(3px, 3px); +} +groupCallMenuPosition: point(-1px, 29px); groupCallActiveButton: IconButton { width: 36px; diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index d9bb38732..449b97d32 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -16,10 +16,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/call_button.h" #include "ui/widgets/call_mute_button.h" #include "ui/widgets/checkbox.h" +#include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/input_fields.h" #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/special_buttons.h" #include "info/profile/info_profile_values.h" // Info::Profile::Value. #include "core/application.h" #include "lang/lang_keys.h" @@ -31,6 +34,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "main/main_session.h" #include "base/event_filter.h" +#include "base/unixtime.h" +#include "base/timer_rpl.h" #include "boxes/peers/edit_participants_box.h" #include "boxes/peers/add_participants_box.h" #include "boxes/peer_lists_box.h" @@ -137,6 +142,81 @@ private: return result; } +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().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(); }); +} + InviteController::InviteController( not_null peer, base::flat_set> alreadyIn) @@ -599,6 +679,7 @@ void GroupPanel::subscribeToChanges(not_null real) { _recordingMark.destroy(); } else if (recording && !_recordingMark) { _recordingMark.create(widget()); + _recordingMark->show(); const auto size = st::groupCallRecordingMark; const auto skip = st::groupCallRecordingMarkSkip; _recordingMark->resize(size + 2 * skip, size + 2 * skip); @@ -631,8 +712,206 @@ void GroupPanel::subscribeToChanges(not_null real) { (recorded ? tr::lng_group_call_recording_started(tr::now) : tr::lng_group_call_recording_stopped(tr::now))); - }, _callLifetime); + }, widget()->lifetime()); validateRecordingMark(real->recordStartDate() != 0); + + const auto showMenu = _peer->canManageGroupCall(); + const auto showUserpic = !showMenu + && (_call->possibleJoinAs().size() > 1); // #TODO calls when to show + if (showMenu) { + _joinAsToggle.destroy(); + if (!_menuToggle) { + _menuToggle.create(widget(), st::groupCallMenuToggle); + _menuToggle->show(); + _menuToggle->setClickedCallback([=] { showMainMenu(); }); + } + } else if (showUserpic) { + _menuToggle.destroy(); + rpl::single( + _call->joinAs() + ) | rpl::then(_call->rejoinEvents( + ) | rpl::map([](const Group::RejoinEvent &event) { + return event.nowJoinAs; + })) | rpl::start_with_next([=](not_null joinAs) { + auto joinAsToggle = object_ptr( + widget(), + joinAs, + Ui::UserpicButton::Role::Custom, + st::groupCallJoinAsToggle); + _joinAsToggle.destroy(); + _joinAsToggle = std::move(joinAsToggle); + _joinAsToggle->show(); + _joinAsToggle->setClickedCallback([=] { + chooseJoinAs(); + }); + }, widget()->lifetime()); + } else { + _menuToggle.destroy(); + _joinAsToggle.destroy(); + } + updateControlsGeometry(); +} + +void GroupPanel::chooseJoinAs() { + const auto context = Group::ChooseJoinAsProcess::Context::Switch; + const auto callback = [=](Group::JoinInfo info) { + if (_call) { + _call->rejoinAs(info); + } + }; + const auto showBox = [=](object_ptr next) { + _layerBg->showBox(std::move(next)); + }; + const auto showToast = [=](QString text) { + Ui::Toast::Show(widget(), text); + }; + _joinAsProcess.start( + _peer, + context, + showBox, + showToast, + callback, + _call->joinAs()); +} + +void GroupPanel::showMainMenu() { + const auto real = _peer->groupCall(); + if (_menu || !_call || !real || real->id() != _call->id()) { + return; + } + _menu.create(widget(), st::groupCallDropdownMenu); + const auto raw = _menu.data(); + raw->setHiddenCallback([=] { + raw->deleteLater(); + if (_menu == raw) { + _menu = nullptr; + _menuToggle->setForceRippled(false); + } + }); + raw->setShowStartCallback([=] { + if (_menu == raw) { + _menuToggle->setForceRippled(true); + } + }); + raw->setHideStartCallback([=] { + if (_menu == raw) { + _menuToggle->setForceRippled(false); + } + }); + _menuToggle->installEventFilter(_menu); + + const auto addEditJoinAs = (_call->possibleJoinAs().size() > 1); // #TODO calls when to show + const auto addEditTitle = _peer->canManageGroupCall(); + const auto addEditRecording = _peer->canManageGroupCall(); + if (addEditJoinAs) { + _menu->addAction(tr::lng_group_call_display_as_header(tr::now), [=] { + chooseJoinAs(); + }); + } + if (addEditTitle) { + _menu->addAction(tr::lng_group_call_edit_title(tr::now), [=] { + const auto done = [=](const QString &title) { + if (_call) { + _call->changeTitle(title); + } + }; + _layerBg->showBox(Box( + EditGroupCallTitleBox, + _peer->name, + real->title(), + done)); + }); + } + if (addEditRecording) { + const auto action = _menu->addAction((real->recordStartDate() != 0) + ? tr::lng_group_call_recording_stop(tr::now) + : tr::lng_group_call_recording_start(tr::now), [=] { + const auto real = _peer->groupCall(); + const auto id = _call ? _call->id() : 0; + if (!real || real->id() != id) { + return; + } + const auto recordStartDate = real->recordStartDate(); + const auto done = [=](QString title) { + if (_call) { + _call->toggleRecording(!recordStartDate, title); + } + }; + if (recordStartDate) { + _layerBg->showBox(Box( + StopGroupCallRecordingBox, + done)); + } else { + _layerBg->showBox(Box( + StartGroupCallRecordingBox, + real->title(), + done)); + } + }); + 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)); + }; + rpl::combine( + real->recordStartDateValue(), + tr::lng_group_call_recording_stop(), + tr::lng_group_call_recording_start() + ) | rpl::map([=](TimeId startDate, QString stop, QString start) { + using namespace rpl::mappers; + return startDate + ? ToRecordDuration( + startDate + ) | rpl::map(stop + '\t' + _1) : rpl::single(start); + }) | rpl::flatten_latest() | rpl::start_with_next([=](QString text) { + action->setText(text); + }, _menu->lifetime()); + } + _menu->addAction(tr::lng_group_call_settings(tr::now), [=] { + if (_call) { + _layerBg->showBox(Box(GroupCallSettingsBox, _call)); + } + }); + _menu->addAction(tr::lng_group_call_end(tr::now), [=] { + if (_call) { + _layerBg->showBox(Box( + LeaveGroupCallBox, + _call, + true, + BoxContext::GroupCallPanel)); + } + }); + + const auto x = st::groupCallMenuPosition.x(); + const auto y = st::groupCallMenuPosition.y(); + if (_menuToggle->x() > widget()->width() / 2) { + _menu->moveToRight(x, y); + _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight); + } else { + _menu->moveToLeft(x, y); + _menu->showAnimated(Ui::PanelAnimation::Origin::TopLeft); + } } void GroupPanel::addMembers() { @@ -847,6 +1126,11 @@ void GroupPanel::initGeometry() { QRect GroupPanel::computeTitleRect() const { const auto skip = st::groupCallTitleTop; + const auto remove = skip + (_menuToggle + ? (_menuToggle->width() + st::groupCallMenuTogglePosition.x()) + : 0) + (_joinAsToggle + ? (_joinAsToggle->width() + st::groupCallMenuTogglePosition.x()) + : 0); const auto width = widget()->width(); #ifdef Q_OS_MAC return QRect(70, 0, width - skip - 70, 28); @@ -854,8 +1138,8 @@ QRect GroupPanel::computeTitleRect() const { const auto controls = _controls->geometry(); 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()); + ? QRect(right, 0, width - right - remove, controls.height()) + : QRect(remove, 0, controls.x() - skip - remove, controls.height()); #endif // !Q_OS_MAC } @@ -893,6 +1177,28 @@ void GroupPanel::updateControlsGeometry() { _settings->moveToLeft((widget()->width() - fullWidth) / 2, buttonsTop); _hangup->moveToRight((widget()->width() - fullWidth) / 2, buttonsTop); refreshTitle(); + +#ifdef Q_OS_MAC + const auto controlsOnTheLeft = true; +#else // Q_OS_MAC + const auto controlsOnTheLeft = _controls->geometry().center().x() + < widget()->width() / 2; +#endif // Q_OS_MAC + const auto menux = st::groupCallMenuTogglePosition.x(); + const auto menuy = st::groupCallMenuTogglePosition.y(); + if (controlsOnTheLeft) { + if (_menuToggle) { + _menuToggle->moveToRight(menux, menuy); + } else if (_joinAsToggle) { + _joinAsToggle->moveToRight(menux, menuy); + } + } else { + if (_menuToggle) { + _menuToggle->moveToLeft(menux, menuy); + } else if (_joinAsToggle) { + _joinAsToggle->moveToLeft(menux, menuy); + } + } } void GroupPanel::refreshTitle() { diff --git a/Telegram/SourceFiles/calls/calls_group_panel.h b/Telegram/SourceFiles/calls/calls_group_panel.h index 6a144d5cb..85fe6fe69 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/calls_group_panel.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "base/object_ptr.h" #include "calls/calls_group_call.h" +#include "calls/calls_choose_join_as.h" #include "ui/effects/animations.h" #include "ui/rp_widget.h" @@ -24,6 +25,7 @@ class GroupCall; namespace Ui { class AbstractButton; +class DropdownMenu; class CallButton; class CallMuteButton; class IconButton; @@ -96,6 +98,8 @@ private: void endCall(); + void showMainMenu(); + void chooseJoinAs(); void addMembers(); void kickMember(not_null user); void kickMemberSure(not_null user); @@ -123,8 +127,12 @@ private: object_ptr _title = { nullptr }; object_ptr _subtitle = { nullptr }; object_ptr _recordingMark = { nullptr }; + object_ptr _menuToggle = { nullptr }; + object_ptr _menu = { nullptr }; + object_ptr _joinAsToggle = { nullptr }; object_ptr _members; rpl::variable _titleText; + Group::ChooseJoinAsProcess _joinAsProcess; 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 925e5fb18..c03c9bb8b 100644 --- a/Telegram/SourceFiles/calls/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/calls_group_settings.cpp @@ -86,81 +86,6 @@ 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().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( @@ -179,7 +104,6 @@ void GroupCallSettingsBox( float micLevel = 0.; Ui::Animations::Simple micLevelAnimation; base::Timer levelUpdateTimer; - Group::ChooseJoinAsProcess joinAsProcess; bool generatingLink = false; }; const auto state = box->lifetime().make_state(); @@ -195,130 +119,16 @@ 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); // #TODO calls when to show - const auto addEditTitle = peer->canManageGroupCall() && goodReal; - const auto addEditRecording = peer->canManageGroupCall() && goodReal; - if (addCheck || addEditJoinAs) { + if (addCheck) { AddSkip(layout); } - const auto editJoinAs = addEditJoinAs - ? AddButton( - layout, - tr::lng_group_call_display_as_header(), - st::groupCallSettingsButton).get() - : nullptr; - const auto editTitle = addEditTitle - ? AddButton( - layout, - tr::lng_group_call_edit_title(), - st::groupCallSettingsButton).get() - : nullptr; - 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 - : AddButtonWithLabel( - layout, - 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([=] { - const auto context = Group::ChooseJoinAsProcess::Context::Switch; - const auto callback = [=](Group::JoinInfo info) { - call->rejoinAs(info); - }; - const auto showBox = [=](object_ptr next) { - box->getDelegate()->show(std::move(next)); - }; - const auto showToast = [=](QString text) { - const auto container = box->getDelegate()->outerContainer(); - Ui::Toast::Show(container, text); - }; - state->joinAsProcess.start( - peer, - context, - showBox, - showToast, - callback, - call->joinAs()); - }); - } - if (editTitle) { - editTitle->setClickedCallback([=] { - const auto done = [=](const QString &title) { - call->changeTitle(title); - box->closeBox(); - }; - box->getDelegate()->show(Box( - EditGroupCallTitleBox, - peer->name, - real->title(), - done)); - }); - } - 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); - 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, tr::lng_group_call_new_muted(), st::groupCallSettingsButton)->toggleOn(rpl::single(joinMuted)) : nullptr; - if (addCheck || addEditJoinAs) { + if (addCheck) { AddSkip(layout); } diff --git a/Telegram/SourceFiles/ui/special_buttons.h b/Telegram/SourceFiles/ui/special_buttons.h index a97220ace..b8ba3a2f6 100644 --- a/Telegram/SourceFiles/ui/special_buttons.h +++ b/Telegram/SourceFiles/ui/special_buttons.h @@ -73,7 +73,7 @@ public: const style::UserpicButton &st); UserpicButton( QWidget *parent, - not_null controller, + not_null<::Window::SessionController*> controller, not_null peer, Role role, const style::UserpicButton &st); @@ -132,7 +132,7 @@ private: void uploadNewPeerPhoto(); const style::UserpicButton &_st; - Window::SessionController *_controller = nullptr; + ::Window::SessionController *_controller = nullptr; PeerData *_peer = nullptr; std::shared_ptr _userpicView; QString _cropTitle;