From b583240d5814ba9fc9cb13f7e808da838b9da78c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Apr 2025 20:23:37 +0400 Subject: [PATCH] Put prioritized users on top of inactive confcall. --- Telegram/SourceFiles/calls/calls.style | 2 +- .../group/calls_group_invite_controller.cpp | 272 ++++++++++++++++-- .../group/calls_group_invite_controller.h | 6 +- Telegram/SourceFiles/main/main_app_config.cpp | 2 - .../window/window_session_controller.cpp | 38 ++- 5 files changed, 284 insertions(+), 36 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 8a50d88f8b..121f93f426 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -649,7 +649,7 @@ createCallListItem: PeerListItem(defaultPeerListItem) { } createCallList: PeerList(defaultPeerList) { item: createCallListItem; - padding: margins(0px, 10px, 0px, 10px); + padding: margins(0px, 6px, 0px, 6px); } groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px); diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index b251c953dd..dbf692dd01 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -112,13 +112,21 @@ private: }; +struct PrioritizedSelector { + object_ptr content = { nullptr }; + Fn overrideKey; + Fn deselect; + Fn activate; +}; + class ConfInviteController final : public ContactsBoxController { public: ConfInviteController( not_null session, ConfInviteStyles st, base::flat_set> alreadyIn, - Fn shareLink); + Fn shareLink, + std::vector> prioritize); [[nodiscard]] rpl::producer hasSelectedValue() const; [[nodiscard]] std::vector requests( @@ -132,15 +140,27 @@ protected: void rowClicked(not_null row) override; void rowElementClicked(not_null row, int element) override; + bool handleDeselectForeignRow(PeerListRowId itemId) override; + bool overrideKeyboardNavigation( + int direction, + int fromIndex, + int toIndex) override; private: [[nodiscard]] int fullCount() const; void toggleRowSelected(not_null row, bool video); + [[nodiscard]] bool toggleRowGetChecked( + not_null row, + bool video); void addShareLinkButton(); + void addPriorityInvites(); const ConfInviteStyles _st; const base::flat_set> _alreadyIn; + const std::vector> _prioritize; const Fn _shareLink; + PrioritizedSelector _prioritizeRows; + base::flat_set> _skip; rpl::variable _hasSelected; base::flat_set> _withVideo; bool _lastSelectWithVideo = false; @@ -290,15 +310,160 @@ void ConfInviteRow::elementsPaint( paintElement(2); } +[[nodiscard]] PrioritizedSelector PrioritizedInviteSelector( + const ConfInviteStyles &st, + std::vector> users, + Fn, bool)> toggleGetChecked, + Fn lastSelectWithVideo, + Fn setLastSelectWithVideo) { + class PrioritizedController final : public PeerListController { + public: + PrioritizedController( + const ConfInviteStyles &st, + std::vector> users, + Fn, bool)> toggleGetChecked, + Fn lastSelectWithVideo, + Fn setLastSelectWithVideo) + : _st(st) + , _users(std::move(users)) + , _toggleGetChecked(std::move(toggleGetChecked)) + , _lastSelectWithVideo(std::move(lastSelectWithVideo)) + , _setLastSelectWithVideo(std::move(setLastSelectWithVideo)) { + Expects(!_users.empty()); + } + + void prepare() override { + for (const auto user : _users) { + delegate()->peerListAppendRow( + std::make_unique(user, _st)); + } + delegate()->peerListRefreshRows(); + } + void loadMoreRows() override { + } + void rowClicked(not_null row) override { + toggleRowSelected(row, _lastSelectWithVideo()); + } + void rowElementClicked( + not_null row, + int element) override { + if (row->checked()) { + static_cast(row.get())->setVideo( + element == 1); + _setLastSelectWithVideo(element == 1); + } else if (element == 1) { + toggleRowSelected(row, true); + } else if (element == 2) { + toggleRowSelected(row, false); + } + } + + void toggleRowSelected(not_null row, bool video) { + delegate()->peerListSetRowChecked( + row, + _toggleGetChecked(row, video)); + } + + Main::Session &session() const override { + return _users.front()->session(); + } + + void toggleFirst() { + Expects(delegate()->peerListFullRowsCount() > 0); + + rowClicked(delegate()->peerListRowAt(0)); + } + + private: + const ConfInviteStyles &_st; + std::vector> _users; + Fn, bool)> _toggleGetChecked; + Fn _lastSelectWithVideo; + Fn _setLastSelectWithVideo; + + }; + + auto result = object_ptr((QWidget*)nullptr); + const auto container = result.data(); + + const auto delegate = container->lifetime().make_state< + PeerListContentDelegateSimple + >(); + const auto controller = container->lifetime( + ).make_state( + st, + users, + toggleGetChecked, + lastSelectWithVideo, + setLastSelectWithVideo); + const auto activate = [=] { + controller->toggleFirst(); + }; + controller->setStyleOverrides(&st::createCallList); + const auto content = container->add(object_ptr( + container, + controller)); + delegate->setContent(content); + controller->setDelegate(delegate); + + Ui::AddDivider(container); + + const auto overrideKey = [=](int direction, int from, int to) { + if (!content->isVisible()) { + return false; + } else if (direction > 0 && from < 0 && to >= 0) { + if (content->hasSelection()) { + const auto was = content->selectedIndex(); + const auto now = content->selectSkip(1).reallyMovedTo; + if (was != now) { + return true; + } + content->clearSelection(); + } else { + content->selectSkip(1); + return true; + } + } else if (direction < 0 && to < 0) { + if (!content->hasSelection()) { + content->selectLast(); + } else if (from >= 0 || content->hasSelection()) { + content->selectSkip(-1); + } + } + return false; + }; + + const auto deselect = [=](PeerListRowId rowId) { + if (const auto row = delegate->peerListFindRow(rowId)) { + delegate->peerListSetRowChecked(row, false); + } + }; + + return { + .content = std::move(result), + .overrideKey = overrideKey, + .deselect = deselect, + .activate = activate, + }; +} + ConfInviteController::ConfInviteController( not_null session, ConfInviteStyles st, base::flat_set> alreadyIn, - Fn shareLink) + Fn shareLink, + std::vector> prioritize) : ContactsBoxController(session) , _st(st) , _alreadyIn(std::move(alreadyIn)) +, _prioritize(std::move(prioritize)) , _shareLink(std::move(shareLink)) { + if (!_shareLink) { + _skip.reserve(_prioritize.size()); + for (const auto user : _prioritize) { + _skip.emplace(user); + } + } } rpl::producer ConfInviteController::hasSelectedValue() const { @@ -322,7 +487,8 @@ std::unique_ptr ConfInviteController::createRow( if (user->isSelf() || user->isBot() || user->isServiceUser() - || user->isInaccessible()) { + || user->isInaccessible() + || _skip.contains(user)) { return nullptr; } auto result = std::make_unique(user, _st); @@ -358,39 +524,87 @@ void ConfInviteController::rowElementClicked( } } +bool ConfInviteController::handleDeselectForeignRow(PeerListRowId itemId) { + if (_prioritizeRows.deselect) { + const auto userId = peerToUser(PeerId(itemId)); + if (ranges::contains(_prioritize, session().data().user(userId))) { + _prioritizeRows.deselect(itemId); + return true; + } + } + return false; +} + +bool ConfInviteController::overrideKeyboardNavigation( + int direction, + int fromIndex, + int toIndex) { + return _prioritizeRows.overrideKey + && _prioritizeRows.overrideKey(direction, fromIndex, toIndex); +} + void ConfInviteController::toggleRowSelected( not_null row, bool video) { + delegate()->peerListSetRowChecked(row, toggleRowGetChecked(row, video)); + + // row may have been destroyed here, from search. + _hasSelected = (delegate()->peerListSelectedRowsCount() > 0); +} + +bool ConfInviteController::toggleRowGetChecked( + not_null row, + bool video) { auto count = fullCount(); const auto conferenceLimit = session().appConfig().confcallSizeLimit(); - if (count < conferenceLimit || row->checked()) { - const auto real = static_cast(row.get()); - if (!row->checked()) { - real->setVideo(video); - _lastSelectWithVideo = video; - } - const auto user = row->peer()->asUser(); - if (!row->checked() && video) { - _withVideo.emplace(user); - } else { - _withVideo.remove(user); - } - delegate()->peerListSetRowChecked(row, !row->checked()); - - // row may have been destroyed here, from search. - _hasSelected = (delegate()->peerListSelectedRowsCount() > 0); - } else { + if (!row->checked() && count >= conferenceLimit) { delegate()->peerListUiShow()->showToast( tr::lng_group_call_invite_limit(tr::now)); + return false; } + const auto real = static_cast(row.get()); + if (!row->checked()) { + real->setVideo(video); + _lastSelectWithVideo = video; + } + const auto user = row->peer()->asUser(); + if (!row->checked() && video) { + _withVideo.emplace(user); + } else { + _withVideo.remove(user); + } + return !row->checked(); } void ConfInviteController::prepareViewHook() { if (_shareLink) { addShareLinkButton(); + } else if (!_prioritize.empty()) { + addPriorityInvites(); } } +void ConfInviteController::addPriorityInvites() { + const auto toggleGetChecked = [=](not_null row, bool video) { + const auto result = toggleRowGetChecked(row, video); + delegate()->peerListSetForeignRowChecked( + row, + result, + anim::type::normal); + + _hasSelected = (delegate()->peerListSelectedRowsCount() > 0); + + return result; + }; + _prioritizeRows = PrioritizedInviteSelector( + _st, + _prioritize, + toggleGetChecked, + [=] { return _lastSelectWithVideo; }, + [=](bool video) { _lastSelectWithVideo = video; }); + delegate()->peerListSetAboveWidget(std::move(_prioritizeRows.content)); +} + void ConfInviteController::addShareLinkButton() { auto button = object_ptr>( nullptr, @@ -571,7 +785,8 @@ object_ptr PrepareInviteBox( &real->session(), ConfInviteDarkStyles(), alreadyIn, - shareLink); + shareLink, + std::vector>()); const auto raw = controller.get(); raw->setStyleOverrides( &st::groupCallInviteMembersList, @@ -742,7 +957,8 @@ object_ptr PrepareInviteBox( &user->session(), ConfInviteDarkStyles(), alreadyIn, - shareLink); + shareLink, + std::vector>()); const auto raw = controller.get(); raw->setStyleOverrides( &st::groupCallInviteMembersList, @@ -806,12 +1022,14 @@ void InitReActivate(not_null box) { object_ptr PrepareInviteToEmptyBox( std::shared_ptr call, - MsgId inviteMsgId) { + MsgId inviteMsgId, + std::vector> prioritize) { auto controller = std::make_unique( &call->session(), ConfInviteDefaultStyles(), base::flat_set>(), - nullptr); + nullptr, + std::move(prioritize)); const auto raw = controller.get(); raw->setStyleOverrides(&st::createCallList); const auto initBox = [=](not_null box) { @@ -845,7 +1063,8 @@ object_ptr PrepareInviteToEmptyBox( object_ptr PrepareCreateCallBox( not_null<::Window::SessionController*> window, Fn created, - MsgId discardedInviteMsgId) { + MsgId discardedInviteMsgId, + std::vector> prioritize) { struct State { bool creatingLink = false; QPointer box; @@ -877,7 +1096,8 @@ object_ptr PrepareCreateCallBox( &window->session(), ConfInviteDefaultStyles(), base::flat_set>(), - discardedInviteMsgId ? Fn() : shareLink); + discardedInviteMsgId ? Fn() : shareLink, + std::move(prioritize)); const auto raw = controller.get(); if (discardedInviteMsgId) { raw->setStyleOverrides(&st::createCallList); diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h index 965c31682b..cbfac6af3e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h @@ -93,11 +93,13 @@ private: [[nodiscard]] object_ptr PrepareInviteToEmptyBox( std::shared_ptr call, - MsgId inviteMsgId); + MsgId inviteMsgId, + std::vector> prioritize); [[nodiscard]] object_ptr PrepareCreateCallBox( not_null<::Window::SessionController*> window, Fn created = nullptr, - MsgId discardedInviteMsgId = 0); + MsgId discardedInviteMsgId = 0, + std::vector> prioritize = {}); } // namespace Calls::Group diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index d925d12787..82fb147939 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -115,8 +115,6 @@ int AppConfig::confcallSizeLimit() const { } bool AppConfig::confcallPrioritizeVP8() const { - AssertIsDebug(); - return false; return get(u"confcall_use_vp8"_q, false); } diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index f5d0d8799f..5e55a41111 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -853,6 +853,30 @@ void SessionNavigation::resolveConferenceCall( resolveConferenceCall({}, inviteMsgId, contextId); } +[[nodiscard]] std::vector> ExtractParticipantsForInvite( + HistoryItem *item) { + if (!item) { + return {}; + } + auto result = std::vector>(); + const auto add = [&](not_null peer) { + if (const auto user = peer->asUser()) { + if (!user->isSelf() + && !ranges::contains(result, not_null(user))) { + result.push_back(user); + } + } + }; + add(item->from()); + const auto media = item->media(); + if (const auto call = media ? media->call() : nullptr) { + for (const auto &peer : call->otherParticipants) { + add(peer); + } + } + return result; +} + void SessionNavigation::resolveConferenceCall( QString slug, MsgId inviteMsgId, @@ -877,6 +901,7 @@ void SessionNavigation::resolveConferenceCall( const auto slug = base::take(_conferenceCallSlug); const auto inviteMsgId = base::take(_conferenceCallInviteMsgId); const auto contextId = base::take(_conferenceCallResolveContextId); + const auto context = session().data().message(contextId); result.data().vcall().match([&](const MTPDgroupCall &data) { const auto call = session().data().sharedConferenceCall( data.vid().v, @@ -896,14 +921,14 @@ void SessionNavigation::resolveConferenceCall( close(); } }; - const auto context = session().data().message(contextId); const auto inviter = context ? context->from()->asUser() : nullptr; if (inviteMsgId && call->participants().empty()) { uiShow()->show(Calls::Group::PrepareInviteToEmptyBox( call, - inviteMsgId)); + inviteMsgId, + ExtractParticipantsForInvite(context))); } else { uiShow()->show(Box( Calls::Group::ConferenceCallJoinConfirm, @@ -917,7 +942,8 @@ void SessionNavigation::resolveConferenceCall( Calls::Group::PrepareCreateCallBox( parentController(), nullptr, - inviteMsgId)); + inviteMsgId, + ExtractParticipantsForInvite(context))); } else { showToast(tr::lng_confcall_link_inactive(tr::now)); } @@ -925,14 +951,16 @@ void SessionNavigation::resolveConferenceCall( }).fail([=] { _conferenceCallRequestId = 0; _conferenceCallSlug = QString(); - _conferenceCallResolveContextId = FullMsgId(); + const auto contextId = base::take(_conferenceCallResolveContextId); + const auto context = session().data().message(contextId); const auto inviteMsgId = base::take(_conferenceCallInviteMsgId); if (inviteMsgId) { uiShow()->show( Calls::Group::PrepareCreateCallBox( parentController(), nullptr, - inviteMsgId)); + inviteMsgId, + ExtractParticipantsForInvite(context))); } else { showToast(tr::lng_confcall_link_inactive(tr::now)); }