diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0b92d24c4d..6c75ca8521 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1846,6 +1846,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_members#other" = "{count} members"; "lng_group_call_no_anonymous" = "Anonymous admins can't join voice chats :("; "lng_group_call_context_mute" = "Mute"; +"lng_group_call_context_unmute" = "Unmute"; "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.style b/Telegram/SourceFiles/calls/calls.style index ee401e81c8..af1af5dd81 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -409,6 +409,36 @@ groupCallHeight: 580px; groupCallRipple: RippleAnimation(defaultRippleAnimation) { color: groupCallMembersBgRipple; } + +groupCallMenu: Menu(defaultMenu) { + itemBg: groupCallMembersBgOver; + itemBgOver: groupCallMembersBgRipple; + itemFg: groupCallMembersFg; + itemFgOver: groupCallMembersFg; + itemFgDisabled: groupCallMemberNotJoinedStatus; + itemFgShortcut: groupCallMemberNotJoinedStatus; + itemFgShortcutOver: groupCallMemberNotJoinedStatus; + itemFgShortcutDisabled: groupCallMemberNotJoinedStatus; + + separatorFg: groupCallMemberNotJoinedStatus; + + arrow: icon {{ "dropdown_submenu_arrow", groupCallMemberNotJoinedStatus }}; + + ripple: groupCallRipple; +} +groupCallMenuShadow: Shadow(defaultEmptyShadow) { + fallback: groupCallMembersBgOver; +} +groupCallPanelAnimation: PanelAnimation(defaultPanelAnimation) { + fadeBg: groupCallMembersBgOver; + shadow: groupCallMenuShadow; +} +groupCallPopupMenu: PopupMenu(defaultPopupMenu) { + shadow: groupCallMenuShadow; + menu: groupCallMenu; + animation: groupCallPanelAnimation; +} + groupCallMembersListItem: PeerListItem(defaultPeerListItem) { button: OutlineButton(defaultPeerListButton) { textBg: groupCallMembersBg; @@ -470,6 +500,7 @@ groupCallMultiSelect: MultiSelect(defaultMultiSelect) { placeholderFg: groupCallMemberNotJoinedStatus; placeholderFgActive: groupCallMemberNotJoinedStatus; placeholderFgError: groupCallMemberNotJoinedStatus; + menu: groupCallPopupMenu; } fieldIcon: icon {{ "box_search", groupCallMemberNotJoinedStatus, point(10px, 9px) }}; fieldCancel: CrossButton(defaultMultiSelectSearchCancel) { @@ -559,9 +590,7 @@ groupCallBox: Box(defaultBox) { textBg: groupCallMembersBg; textBgOver: groupCallMembersBgOver; - ripple: RippleAnimation(defaultRippleAnimation) { - color: groupCallMembersBgRipple; - } + ripple: groupCallRipple; } margin: margins(0px, 56px, 0px, 10px); bg: groupCallMembersBg; @@ -612,9 +641,7 @@ groupCallSettingsButton: SettingsButton { height: 20px; padding: margins(22px, 10px, 22px, 8px); - ripple: RippleAnimation(defaultRippleAnimation) { - color: groupCallMembersBgRipple; - } + ripple: groupCallRipple; } groupCallSettingsAttentionButton: SettingsButton(groupCallSettingsButton) { textFg: attentionButtonFg; diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index 19e516b8ae..e4591ea13a 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -18,12 +18,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/text/text_utilities.h" #include "ui/effects/ripple_animation.h" +#include "core/application.h" // Core::App().domain, Core::App().activeWindow. +#include "main/main_domain.h" // Core::App().domain().activate. #include "main/main_session.h" #include "base/timer.h" #include "boxes/peers/edit_participants_box.h" #include "lang/lang_keys.h" -#include "facades.h" // Ui::showPeerHistory. -#include "mainwindow.h" // App::wnd()->activate. +#include "window/window_controller.h" // Controller::sessionController. +#include "window/window_session_controller.h" #include "styles/style_calls.h" namespace Calls { @@ -107,7 +109,9 @@ class MembersController final : public PeerListController , public base::has_weak_ptr { public: - explicit MembersController(not_null call); + MembersController( + not_null call, + not_null menuParent); using MuteRequest = GroupMembers::MuteRequest; @@ -124,6 +128,8 @@ public: return _fullCount.value(); } [[nodiscard]] rpl::producer toggleMuteRequests() const; + [[nodiscard]] auto kickMemberRequests() const + -> rpl::producer>; private: [[nodiscard]] std::unique_ptr createSelfRow() const; @@ -155,8 +161,11 @@ private: uint64 _realId = 0; rpl::event_stream _toggleMuteRequests; + rpl::event_stream> _kickMemberRequests; rpl::variable _fullCount = 1; - Ui::BoxPointer _addBox; + + not_null _menuParent; + base::unique_qptr _menu; //base::flat_map, crl::time> _repaintByTimer; //base::Timer _repaintTimer; @@ -318,9 +327,12 @@ void Row::stopLastActionRipple() { } } -MembersController::MembersController(not_null call) +MembersController::MembersController( + not_null call, + not_null menuParent) : _call(call) -, _channel(call->channel()) { +, _channel(call->channel()) +, _menuParent(menuParent) { //, _repaintTimer([=] { repaintByTimer(); }) { setupListChangeViewers(call); } @@ -586,25 +598,27 @@ void MembersController::loadMoreRows() { } auto MembersController::toggleMuteRequests() const --> rpl::producer { +-> rpl::producer { return _toggleMuteRequests.events(); } +auto MembersController::kickMemberRequests() const +-> rpl::producer>{ + return _kickMemberRequests.events(); +} + void MembersController::rowClicked(not_null row) { - Ui::showPeerHistory(row->peer(), ShowAtUnreadMsgId); - App::wnd()->activate(); + if (_menu) { + _menu->deleteLater(); + _menu = nullptr; + } + _menu = rowContextMenu(_menuParent, row); + _menu->popup(QCursor::pos()); } void MembersController::rowActionClicked( not_null row) { - Expects(row->peer()->isUser()); - - const auto real = static_cast(row.get()); - const auto mute = (real->state() != Row::State::Muted); - _toggleMuteRequests.fire(MuteRequest{ - .user = row->peer()->asUser(), - .mute = mute, - }); + rowClicked(row); } base::unique_qptr MembersController::rowContextMenu( @@ -612,8 +626,86 @@ base::unique_qptr MembersController::rowContextMenu( not_null row) { Expects(row->peer()->isUser()); + const auto real = static_cast(row.get()); const auto user = row->peer()->asUser(); - return nullptr; + auto result = base::make_unique_q( + parent, + st::groupCallPopupMenu); + + const auto mute = (real->state() != Row::State::Muted); + const auto toggleMute = crl::guard(this, [=] { + _toggleMuteRequests.fire(MuteRequest{ + .user = user, + .mute = mute, + }); + }); + + const auto session = &user->session(); + const auto getCurrentWindow = [=]() -> Window::SessionController* { + if (const auto window = Core::App().activeWindow()) { + if (const auto controller = window->sessionController()) { + if (&controller->session() == session) { + return controller; + } + } + } + return nullptr; + }; + const auto getWindow = [=] { + if (const auto current = getCurrentWindow()) { + return current; + } else if (&Core::App().domain().active() != &session->account()) { + Core::App().domain().activate(&session->account()); + } + return getCurrentWindow(); + }; + const auto performOnMainWindow = [=](auto callback) { + if (const auto window = getWindow()) { + if (_menu) { + _menu->discardParentReActivate(); + + // We must hide PopupMenu before we activate the MainWindow, + // otherwise we set focus in field inside MainWindow and then + // PopupMenu::hide activates back the group call panel :( + _menu = nullptr; + } + callback(window); + window->widget()->activate(); + } + }; + const auto showProfile = [=] { + performOnMainWindow([=](not_null window) { + window->showPeerInfo(user); + }); + }; + const auto showHistory = [=] { + performOnMainWindow([=](not_null window) { + window->showPeerHistory(user); + }); + }; + const auto removeFromGroup = crl::guard(this, [=] { + _kickMemberRequests.fire_copy(user); + }); + + if (!user->isSelf() && _channel->canManageCall()) { + result->addAction( + (mute + ? tr::lng_group_call_context_mute(tr::now) + : tr::lng_group_call_context_unmute(tr::now)), + toggleMute); + } + result->addAction( + tr::lng_context_view_profile(tr::now), + showProfile); + result->addAction( + tr::lng_context_send_message(tr::now), + showHistory); + if (_channel->canRestrictUser(user)) { + result->addAction( + tr::lng_context_remove_from_group(tr::now), + removeFromGroup); + } + return result; } std::unique_ptr MembersController::createSelfRow() const { @@ -633,15 +725,16 @@ std::unique_ptr MembersController::createRow( } // namespace GroupMembers::GroupMembers( - QWidget *parent, + not_null parent, not_null call) : RpWidget(parent) , _call(call) , _scroll(this, st::defaultSolidScroll) -, _listController(std::make_unique(call)) { +, _listController(std::make_unique(call, parent)) { setupHeader(call); setupList(); setContent(_list); + setupFakeRoundCorners(); _listController->setDelegate(static_cast(this)); paintRequest( @@ -660,6 +753,12 @@ auto GroupMembers::toggleMuteRequests() const _listController.get())->toggleMuteRequests(); } +auto GroupMembers::kickMemberRequests() const +-> rpl::producer> { + return static_cast( + _listController.get())->kickMemberRequests(); +} + int GroupMembers::desiredHeight() const { auto desired = _header ? _header->height() : 0; auto count = [&] { @@ -790,6 +889,10 @@ void GroupMembers::updateHeaderControlsGeometry(int newWidth) { _title->moveToLeft(0, 0); } +void GroupMembers::setupFakeRoundCorners() { + +} + void GroupMembers::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { diff --git a/Telegram/SourceFiles/calls/calls_group_members.h b/Telegram/SourceFiles/calls/calls_group_members.h index 1e02d6a768..de42404ee2 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.h +++ b/Telegram/SourceFiles/calls/calls_group_members.h @@ -26,7 +26,7 @@ class GroupMembers final , private PeerListContentDelegate { public: GroupMembers( - QWidget *parent, + not_null parent, not_null call); struct MuteRequest { @@ -37,6 +37,8 @@ public: [[nodiscard]] int desiredHeight() const; [[nodiscard]] rpl::producer desiredHeightValue() const override; [[nodiscard]] rpl::producer toggleMuteRequests() const; + [[nodiscard]] auto kickMemberRequests() const + -> rpl::producer>; [[nodiscard]] rpl::producer<> addMembersRequests() const { return _addMemberRequests.events(); } @@ -68,6 +70,7 @@ private: void setupHeader(not_null call); object_ptr setupTitle(not_null call); void setupList(); + void setupFakeRoundCorners(); void setupButtons(not_null call); diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index 0364dd3f35..5763498fa4 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 "base/event_filter.h" #include "boxes/peers/edit_participants_box.h" #include "app.h" +#include "apiwrap.h" // api().kickParticipant. #include "styles/style_calls.h" #include "styles/style_layers.h" @@ -360,6 +361,11 @@ void GroupPanel::initWithCall(GroupCall *call) { } }, _callLifetime); + _members->kickMemberRequests( + ) | rpl::start_with_next([=](not_null user) { + kickMember(user); + }, _callLifetime); + _members->addMembersRequests( ) | rpl::start_with_next([=] { if (_call) { @@ -455,6 +461,43 @@ void GroupPanel::addMembers() { _layerBg->showBox(Box(std::move(controller), initBox)); } +void GroupPanel::kickMember(not_null user) { + _layerBg->showBox(Box([=](not_null box) { + box->addRow( + object_ptr( + box.get(), + tr::lng_profile_sure_kick( + tr::now, + lt_user, + user->firstName), + st::groupCallBoxLabel), + style::margins( + st::boxRowPadding.left(), + st::boxPadding.top(), + st::boxRowPadding.right(), + st::boxPadding.bottom())); + box->addButton(tr::lng_box_remove(), [=] { + box->closeBox(); + kickMemberSure(user); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + })); +} + +void GroupPanel::kickMemberSure(not_null user) { + const auto currentRestrictedRights = [&]() -> MTPChatBannedRights { + const auto it = _channel->mgInfo->lastRestricted.find(user); + return (it != _channel->mgInfo->lastRestricted.cend()) + ? it->second.rights + : MTP_chatBannedRights(MTP_flags(0), MTP_int(0)); + }(); + + _channel->session().api().kickParticipant( + _channel, + user, + currentRestrictedRights); +} + void GroupPanel::initLayout() { initGeometry(); diff --git a/Telegram/SourceFiles/calls/calls_group_panel.h b/Telegram/SourceFiles/calls/calls_group_panel.h index 691893d55e..6fd1f21905 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/calls_group_panel.h @@ -92,6 +92,8 @@ private: void hangup(bool discardCallChecked); void addMembers(); + void kickMember(not_null user); + void kickMemberSure(not_null user); [[nodiscard]] int computeMembersListTop() const; [[nodiscard]] std::optional computeTitleRect() const; void refreshTitle(); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 79ea651127..e0fb1129d1 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 79ea651127962fff366e9d76ce4d932490b30158 +Subproject commit e0fb1129d145054410476bd83d1cecf5c2a2644d