From 92bc27805272b1cca7ba00be5583f13e980aacc4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 21 Dec 2020 23:43:23 +0400 Subject: [PATCH] Allow inviting contacts to voice chats. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 5 + Telegram/SourceFiles/apiwrap.cpp | 8 +- Telegram/SourceFiles/apiwrap.h | 3 +- .../SourceFiles/boxes/add_contact_box.cpp | 5 +- .../SourceFiles/boxes/edit_privacy_box.cpp | 22 +- .../boxes/filters/edit_filter_box.cpp | 4 +- .../boxes/filters/edit_filter_chats_list.cpp | 14 +- .../boxes/filters/edit_filter_chats_list.h | 4 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 89 +++- Telegram/SourceFiles/boxes/peer_list_box.h | 28 +- .../boxes/peer_list_controllers.cpp | 55 ++- .../SourceFiles/boxes/peer_list_controllers.h | 34 +- Telegram/SourceFiles/boxes/peer_lists_box.cpp | 429 ++++++++++++++++++ Telegram/SourceFiles/boxes/peer_lists_box.h | 101 +++++ .../boxes/peers/add_participants_box.cpp | 20 +- .../boxes/peers/add_participants_box.h | 12 +- Telegram/SourceFiles/calls/calls.style | 8 +- .../SourceFiles/calls/calls_group_members.cpp | 7 +- .../SourceFiles/calls/calls_group_members.h | 2 +- .../SourceFiles/calls/calls_group_panel.cpp | 320 ++++++++++--- Telegram/SourceFiles/data/data_group_call.cpp | 2 +- .../info_common_groups_inner_widget.cpp | 4 - .../info_common_groups_inner_widget.h | 1 - .../polls/info_polls_results_inner_widget.cpp | 6 - .../info/profile/info_profile_actions.cpp | 2 +- .../info/profile/info_profile_members.cpp | 4 - .../info/profile/info_profile_members.h | 1 - .../SourceFiles/platform/win/windows_dlls.cpp | 1 + .../settings/settings_privacy_controllers.cpp | 16 +- .../SourceFiles/window/window_peer_menu.cpp | 6 +- .../window/window_session_controller.cpp | 4 +- 32 files changed, 989 insertions(+), 230 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/peer_lists_box.cpp create mode 100644 Telegram/SourceFiles/boxes/peer_lists_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 2ada5253d..d02ca0028 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -235,6 +235,8 @@ PRIVATE boxes/peer_list_box.h boxes/peer_list_controllers.cpp boxes/peer_list_controllers.h + boxes/peer_lists_box.cpp + boxes/peer_lists_box.h boxes/passcode_box.cpp boxes/passcode_box.h boxes/photo_crop_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4ced0747c..cedef2dfa 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1835,6 +1835,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_invited_status" = "invited"; "lng_group_call_invite_title" = "Invite members"; "lng_group_call_invite_button" = "Invite"; +"lng_group_call_add_to_group_one" = "{user} isn't a member of «{group}» yet. Add them to the group?"; +"lng_group_call_add_to_group_some" = "Some of those users aren't members of «{group}» yet. Add them to the group?"; +"lng_group_call_add_to_group_all" = "Those users aren't members of «{group}» yet. Add them to the group?"; +"lng_group_call_invite_members" = "Group members"; +"lng_group_call_invite_search_results" = "Search results"; "lng_group_call_new_muted" = "Mute new members"; "lng_group_call_speakers" = "Speakers"; "lng_group_call_microphone" = "Microphone"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 7c94a819f..fa6e25ac9 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3458,7 +3458,8 @@ void ApiWrap::checkForUnreadMentions( void ApiWrap::addChatParticipants( not_null peer, - const std::vector> &users) { + const std::vector> &users, + Fn done) { if (const auto chat = peer->asChat()) { for (const auto user : users) { request(MTPmessages_AddChatUser( @@ -3467,8 +3468,10 @@ void ApiWrap::addChatParticipants( MTP_int(kForwardMessagesOnAdd) )).done([=](const MTPUpdates &result) { applyUpdates(result); + if (done) done(true); }).fail([=](const RPCError &error) { ShowAddParticipantsError(error.type(), peer, { 1, user }); + if (done) done(false); }).afterDelay(crl::time(5)).send(); } } else if (const auto channel = peer->asChannel()) { @@ -3480,14 +3483,17 @@ void ApiWrap::addChatParticipants( auto list = QVector(); list.reserve(qMin(int(users.size()), int(kMaxUsersPerInvite))); const auto send = [&] { + const auto callback = base::take(done); request(MTPchannels_InviteToChannel( channel->inputChannel, MTP_vector(list) )).done([=](const MTPUpdates &result) { applyUpdates(result); requestParticipantsCountDelayed(channel); + if (callback) callback(true); }).fail([=](const RPCError &error) { ShowAddParticipantsError(error.type(), peer, users); + if (callback) callback(false); }).afterDelay(crl::time(5)).send(); }; for (const auto user : users) { diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 24c0d0960..dbe80b6f0 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -366,7 +366,8 @@ public: Fn callbackNotModified = nullptr); void addChatParticipants( not_null peer, - const std::vector> &users); + const std::vector> &users, + Fn done = nullptr); rpl::producer sendActions() const { return _sendActions.events(); diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index c0419a5ef..599edfc10 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -632,7 +632,7 @@ void GroupInfoBox::submit() { not_null box) { auto create = [box, title, weak] { if (weak) { - auto rows = box->peerListCollectSelectedRows(); + auto rows = box->collectSelectedRows(); if (!rows.empty()) { weak->createGroup(box, title, rows); } @@ -643,7 +643,8 @@ void GroupInfoBox::submit() { }; Ui::show( Box( - std::make_unique(_navigation), + std::make_unique( + &_navigation->session()), std::move(initBox)), Ui::LayerOption::KeepOther); } diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 3be617c73..453840772 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -33,38 +33,36 @@ namespace { class PrivacyExceptionsBoxController : public ChatsListBoxController { public: PrivacyExceptionsBoxController( - not_null navigation, + not_null session, rpl::producer title, const std::vector> &selected); Main::Session &session() const override; void rowClicked(not_null row) override; - std::vector> getResult() const; - protected: void prepareViewHook() override; std::unique_ptr createRow(not_null history) override; private: - not_null _navigation; + const not_null _session; rpl::producer _title; std::vector> _selected; }; PrivacyExceptionsBoxController::PrivacyExceptionsBoxController( - not_null navigation, + not_null session, rpl::producer title, const std::vector> &selected) -: ChatsListBoxController(navigation) -, _navigation(navigation) +: ChatsListBoxController(session) +, _session(session) , _title(std::move(title)) , _selected(selected) { } Main::Session &PrivacyExceptionsBoxController::session() const { - return _navigation->session(); + return *_session; } void PrivacyExceptionsBoxController::prepareViewHook() { @@ -72,10 +70,6 @@ void PrivacyExceptionsBoxController::prepareViewHook() { delegate()->peerListAddSelectedPeers(_selected); } -std::vector> PrivacyExceptionsBoxController::getResult() const { - return delegate()->peerListCollectSelectedRows(); -} - void PrivacyExceptionsBoxController::rowClicked(not_null row) { const auto peer = row->peer(); @@ -146,13 +140,13 @@ void EditPrivacyBox::editExceptions( Exception exception, Fn done) { auto controller = std::make_unique( - _window, + &_window->session(), _controller->exceptionBoxTitle(exception), exceptions(exception)); auto initBox = [=, controller = controller.get()]( not_null box) { box->addButton(tr::lng_settings_save(), crl::guard(this, [=] { - exceptions(exception) = controller->getResult(); + exceptions(exception) = box->collectSelectedRows(); const auto type = [&] { switch (exception) { case Exception::Always: return Exception::Never; diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 40cc48323..91c800534 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -320,7 +320,7 @@ void EditExceptions( const auto include = (options & Flag::Contacts) != Flags(0); const auto rules = data->current(); auto controller = std::make_unique( - window, + &window->session(), (include ? tr::lng_filters_include_title() : tr::lng_filters_exclude_title()), @@ -331,7 +331,7 @@ void EditExceptions( auto initBox = [=](not_null box) { box->setCloseByOutsideClick(false); box->addButton(tr::lng_settings_save(), crl::guard(context, [=] { - const auto peers = box->peerListCollectSelectedRows(); + const auto peers = box->collectSelectedRows(); const auto rules = data->current(); auto &&histories = ranges::view::all( peers diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index 33ff95351..fd8c9cc6d 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -68,7 +68,6 @@ public: void peerListSetAdditionalTitle(rpl::producer title) override; bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; - std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; void peerListAddSelectedPeerInBunch( not_null peer) override; @@ -209,11 +208,6 @@ int TypeDelegate::peerListSelectedRowsCount() { return 0; } -auto TypeDelegate::peerListCollectSelectedRows() --> std::vector> { - return {}; -} - void TypeDelegate::peerListScrollToTop() { } @@ -347,13 +341,13 @@ void PaintFilterChatsTypeIcon( } EditFilterChatsListController::EditFilterChatsListController( - not_null navigation, + not_null session, rpl::producer title, Flags options, Flags selected, const base::flat_set> &peers) -: ChatsListBoxController(navigation) -, _navigation(navigation) +: ChatsListBoxController(session) +, _session(session) , _title(std::move(title)) , _peers(peers) , _options(options) @@ -361,7 +355,7 @@ EditFilterChatsListController::EditFilterChatsListController( } Main::Session &EditFilterChatsListController::session() const { - return _navigation->session(); + return *_session; } void EditFilterChatsListController::rowClicked(not_null row) { diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h index e993cafb7..4dc777eb2 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h @@ -41,7 +41,7 @@ public: using Flags = Data::ChatFilter::Flags; EditFilterChatsListController( - not_null navigation, + not_null session, rpl::producer title, Flags options, Flags selected, @@ -64,7 +64,7 @@ private: void updateTitle(); - const not_null _navigation; + const not_null _session; rpl::producer _title; base::flat_set> _peers; Flags _options; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 5a957773b..997876596 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -399,7 +399,7 @@ int PeerListBox::peerListSelectedRowsCount() { return _select ? _select->entity()->getItemsCount() : 0; } -auto PeerListBox::peerListCollectSelectedRows() +auto PeerListBox::collectSelectedRows() -> std::vector> { auto result = std::vector>(); auto items = _select @@ -982,6 +982,18 @@ void PeerListContent::setAboveWidget(object_ptr widget) { } } +void PeerListContent::setAboveSearchWidget(object_ptr widget) { + _aboveSearchWidget = std::move(widget); + if (_aboveSearchWidget) { + _aboveSearchWidget->setParent(this); + } +} + +void PeerListContent::setHideEmpty(bool hide) { + _hideEmpty = hide; + resizeToWidth(width()); +} + void PeerListContent::setBelowWidget(object_ptr widget) { _belowWidget = std::move(widget); if (_belowWidget) { @@ -990,6 +1002,9 @@ void PeerListContent::setBelowWidget(object_ptr widget) { } int PeerListContent::labelHeight() const { + if (_hideEmpty && !shownRowsCount()) { + return 0; + } auto computeLabelHeight = [](auto &label) { if (!label) { return 0; @@ -1082,34 +1097,45 @@ void PeerListContent::paintEvent(QPaintEvent *e) { } int PeerListContent::resizeGetHeight(int newWidth) { + const auto rowsCount = shownRowsCount(); + const auto hideAll = !rowsCount && _hideEmpty; _aboveHeight = 0; if (_aboveWidget) { _aboveWidget->resizeToWidth(newWidth); _aboveWidget->moveToLeft(0, 0, newWidth); - if (showingSearch()) { + if (hideAll || showingSearch()) { _aboveWidget->hide(); } else { _aboveWidget->show(); _aboveHeight = _aboveWidget->height(); } } - const auto rowsCount = shownRowsCount(); + if (_aboveSearchWidget) { + _aboveSearchWidget->resizeToWidth(newWidth); + _aboveSearchWidget->moveToLeft(0, 0, newWidth); + if (hideAll || !showingSearch()) { + _aboveSearchWidget->hide(); + } else { + _aboveSearchWidget->show(); + _aboveHeight = _aboveSearchWidget->height(); + } + } const auto labelTop = rowsTop() + qMax(1, shownRowsCount()) * _rowHeight; const auto labelWidth = newWidth - 2 * st::contactsPadding.left(); if (_description) { _description->resizeToWidth(labelWidth); _description->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth); - _description->setVisible(!showingSearch()); + _description->setVisible(!hideAll && !showingSearch()); } if (_searchNoResults) { _searchNoResults->resizeToWidth(labelWidth); _searchNoResults->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth); - _searchNoResults->setVisible(showingSearch() && _filterResults.empty() && !_controller->isSearchLoading()); + _searchNoResults->setVisible(!hideAll && showingSearch() && _filterResults.empty() && !_controller->isSearchLoading()); } if (_searchLoading) { _searchLoading->resizeToWidth(labelWidth); _searchLoading->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth); - _searchLoading->setVisible(showingSearch() && _filterResults.empty() && _controller->isSearchLoading()); + _searchLoading->setVisible(!hideAll && showingSearch() && _filterResults.empty() && _controller->isSearchLoading()); } const auto label = labelHeight(); const auto belowTop = (label > 0 || rowsCount > 0) @@ -1119,7 +1145,7 @@ int PeerListContent::resizeGetHeight(int newWidth) { if (_belowWidget) { _belowWidget->resizeToWidth(newWidth); _belowWidget->moveToLeft(0, belowTop, newWidth); - if (showingSearch()) { + if (hideAll || showingSearch()) { _belowWidget->hide(); } else { _belowWidget->show(); @@ -1351,15 +1377,18 @@ crl::time PeerListContent::paintRow( return (refreshStatusAt - ms); } -void PeerListContent::selectSkip(int direction) { - if (_pressed.index.value >= 0) { - return; +PeerListContent::SkipResult PeerListContent::selectSkip(int direction) { + if (hasPressed()) { + return { _selected.index.value, _selected.index.value }; } _mouseSelection = false; _lastMousePosition = std::nullopt; auto newSelectedIndex = _selected.index.value + direction; + auto result = SkipResult(); + result.shouldMoveTo = newSelectedIndex; + auto rowsCount = shownRowsCount(); auto index = 0; auto firstEnabled = -1, lastEnabled = -1; @@ -1415,14 +1444,36 @@ void PeerListContent::selectSkip(int direction) { } update(); + + _selectedIndex = _selected.index.value; + result.reallyMovedTo = _selected.index.value; + return result; } void PeerListContent::selectSkipPage(int height, int direction) { auto rowsToSkip = height / _rowHeight; - if (!rowsToSkip) return; + if (!rowsToSkip) { + return; + } selectSkip(rowsToSkip * direction); } +rpl::producer PeerListContent::selectedIndexValue() const { + return _selectedIndex.value(); +} + +bool PeerListContent::hasSelection() const { + return _selected.index.value >= 0; +} + +bool PeerListContent::hasPressed() const { + return _pressed.index.value >= 0; +} + +void PeerListContent::clearSelection() { + setSelected(Selected()); +} + void PeerListContent::loadProfilePhotos() { if (_visibleTop >= _visibleBottom) return; @@ -1569,14 +1620,17 @@ void PeerListContent::setSearchQuery( clearSearchRows(); } -void PeerListContent::submitted() { +bool PeerListContent::submitted() { if (const auto row = getRow(_selected.index)) { _controller->rowClicked(row); + return true; } else if (showingSearch()) { if (const auto row = getRow(RowIndex(0))) { _controller->rowClicked(row); + return true; } } + return false; } void PeerListContent::visibleTopBottomUpdated( @@ -1590,11 +1644,14 @@ void PeerListContent::visibleTopBottomUpdated( void PeerListContent::setSelected(Selected selected) { updateRow(_selected.index); - if (_selected != selected) { - _selected = selected; - updateRow(_selected.index); - setCursor(_selected.action ? style::cur_pointer : style::cur_default); + if (_selected == selected) { + return; } + _selected = selected; + updateRow(_selected.index); + setCursor(_selected.action ? style::cur_pointer : style::cur_default); + + _selectedIndex = _selected.index.value; } void PeerListContent::setContexted(Selected contexted) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index f5cedcbbc..0a8332c4e 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -254,10 +254,12 @@ class PeerListDelegate { public: virtual void peerListSetTitle(rpl::producer title) = 0; virtual void peerListSetAdditionalTitle(rpl::producer title) = 0; + virtual void peerListSetHideEmpty(bool hide) = 0; virtual void peerListSetDescription(object_ptr description) = 0; virtual void peerListSetSearchLoading(object_ptr loading) = 0; virtual void peerListSetSearchNoResults(object_ptr noResults) = 0; virtual void peerListSetAboveWidget(object_ptr aboveWidget) = 0; + virtual void peerListSetAboveSearchWidget(object_ptr aboveWidget) = 0; virtual void peerListSetBelowWidget(object_ptr belowWidget) = 0; virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0; virtual void peerListAppendRow(std::unique_ptr row) = 0; @@ -299,7 +301,6 @@ public: } virtual int peerListSelectedRowsCount() = 0; - virtual std::vector> peerListCollectSelectedRows() = 0; virtual std::unique_ptr peerListSaveState() const = 0; virtual void peerListRestoreState( std::unique_ptr state) = 0; @@ -499,13 +500,20 @@ public: QWidget *parent, not_null controller); - void selectSkip(int direction); + struct SkipResult { + int shouldMoveTo = 0; + int reallyMovedTo = 0; + }; + SkipResult selectSkip(int direction); void selectSkipPage(int height, int direction); + [[nodiscard]] rpl::producer selectedIndexValue() const; + [[nodiscard]] bool hasSelection() const; + [[nodiscard]] bool hasPressed() const; void clearSelection(); void searchQueryChanged(QString query); - void submitted(); + bool submitted(); // Interface for the controller. void appendRow(std::unique_ptr row); @@ -525,7 +533,9 @@ public: void setSearchLoading(object_ptr loading); void setSearchNoResults(object_ptr noResults); void setAboveWidget(object_ptr widget); + void setAboveSearchWidget(object_ptr widget); void setBelowWidget(object_ptr width); + void setHideEmpty(bool hide); void refreshRows(); void setSearchMode(PeerListSearchMode mode); @@ -667,6 +677,7 @@ private: Selected _selected; Selected _pressed; Selected _contexted; + rpl::variable _selectedIndex = -1; bool _mouseSelection = false; std::optional _lastMousePosition; Qt::MouseButton _pressButton = Qt::LeftButton; @@ -685,7 +696,9 @@ private: int _aboveHeight = 0; int _belowHeight = 0; + bool _hideEmpty = false; object_ptr _aboveWidget = { nullptr }; + object_ptr _aboveSearchWidget = { nullptr }; object_ptr _belowWidget = { nullptr }; object_ptr _description = { nullptr }; object_ptr _searchNoResults = { nullptr }; @@ -703,6 +716,9 @@ public: _content = content; } + void peerListSetHideEmpty(bool hide) override { + _content->setHideEmpty(hide); + } void peerListAppendRow( std::unique_ptr row) override { _content->appendRow(std::move(row)); @@ -767,6 +783,9 @@ public: void peerListSetAboveWidget(object_ptr aboveWidget) override { _content->setAboveWidget(std::move(aboveWidget)); } + void peerListSetAboveSearchWidget(object_ptr aboveWidget) override { + _content->setAboveSearchWidget(std::move(aboveWidget)); + } void peerListSetBelowWidget(object_ptr belowWidget) override { _content->setBelowWidget(std::move(belowWidget)); } @@ -824,6 +843,8 @@ public: std::unique_ptr controller, Fn)> init); + [[nodiscard]] std::vector> collectSelectedRows(); + void peerListSetTitle(rpl::producer title) override { setTitle(std::move(title)); } @@ -840,7 +861,6 @@ public: anim::type animated) override; bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; - std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; protected: diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 4b8041fab..e16f9136a 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "history/history.h" #include "dialogs/dialogs_main_list.h" -#include "window/window_session_controller.h" +#include "window/window_session_controller.h" // onShowAddContact() #include "facades.h" #include "styles/style_boxes.h" #include "styles/style_profile.h" @@ -115,7 +115,8 @@ object_ptr PrepareContactsBox( [=] { controller->widget()->onShowAddContact(); }); }; return Box( - std::make_unique(controller), + std::make_unique( + &sessionController->session()), std::move(delegate)); } @@ -159,9 +160,9 @@ void PeerListRowWithLink::paintAction( } PeerListGlobalSearchController::PeerListGlobalSearchController( - not_null navigation) -: _navigation(navigation) -, _api(&_navigation->session().mtp()) { + not_null session) +: _session(session) +, _api(&session->mtp()) { _timer.setCallback([this] { searchOnServer(); }); } @@ -210,8 +211,8 @@ void PeerListGlobalSearchController::searchDone( auto &contacts = result.c_contacts_found(); auto query = _query; if (requestId) { - _navigation->session().data().processUsers(contacts.vusers()); - _navigation->session().data().processChats(contacts.vchats()); + _session->data().processUsers(contacts.vusers()); + _session->data().processChats(contacts.vchats()); auto it = _queries.find(requestId); if (it != _queries.cend()) { query = it->second; @@ -221,7 +222,7 @@ void PeerListGlobalSearchController::searchDone( } const auto feedList = [&](const MTPVector &list) { for (const auto &mtpPeer : list.v) { - const auto peer = _navigation->session().data().peerLoaded( + const auto peer = _session->data().peerLoaded( peerFromMTP(mtpPeer)); if (peer) { delegate()->peerListSearchAddRow(peer); @@ -246,9 +247,9 @@ ChatsListBoxController::Row::Row(not_null history) } ChatsListBoxController::ChatsListBoxController( - not_null navigation) + not_null session) : ChatsListBoxController( - std::make_unique(navigation)) { + std::make_unique(session)) { } ChatsListBoxController::ChatsListBoxController( @@ -354,21 +355,21 @@ bool ChatsListBoxController::appendRow(not_null history) { } ContactsBoxController::ContactsBoxController( - not_null navigation) -: PeerListController( - std::make_unique(navigation)) -, _navigation(navigation) { + not_null session) +: ContactsBoxController( + session, + std::make_unique(session)) { } ContactsBoxController::ContactsBoxController( - not_null navigation, + not_null session, std::unique_ptr searchController) : PeerListController(std::move(searchController)) -, _navigation(navigation) { +, _session(session) { } Main::Session &ContactsBoxController::session() const { - return _navigation->session(); + return *_session; } void ContactsBoxController::prepare() { @@ -435,26 +436,24 @@ bool ContactsBoxController::appendRow(not_null user) { return false; } -std::unique_ptr ContactsBoxController::createRow(not_null user) { +std::unique_ptr ContactsBoxController::createRow( + not_null user) { return std::make_unique(user); } -void AddBotToGroupBoxController::Start( - not_null navigation, - not_null bot) { +void AddBotToGroupBoxController::Start(not_null bot) { auto initBox = [=](not_null box) { box->addButton(tr::lng_cancel(), [box] { box->closeBox(); }); }; Ui::show(Box( - std::make_unique(navigation, bot), + std::make_unique(bot), std::move(initBox))); } AddBotToGroupBoxController::AddBotToGroupBoxController( - not_null navigation, not_null bot) : ChatsListBoxController(SharingBotGame(bot) - ? std::make_unique(navigation) + ? std::make_unique(&bot->session()) : nullptr) , _bot(bot) { } @@ -572,15 +571,15 @@ void AddBotToGroupBoxController::prepareViewHook() { } ChooseRecipientBoxController::ChooseRecipientBoxController( - not_null navigation, + not_null session, FnMut)> callback) -: ChatsListBoxController(navigation) -, _navigation(navigation) +: ChatsListBoxController(session) +, _session(session) , _callback(std::move(callback)) { } Main::Session &ChooseRecipientBoxController::session() const { - return _navigation->session(); + return *_session; } void ChooseRecipientBoxController::prepareViewHook() { diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index 17d495241..3794d1eab 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -32,7 +32,6 @@ class History; namespace Window { class SessionController; -class SessionNavigation; } // namespace Window [[nodiscard]] object_ptr PrepareContactsBox( @@ -65,8 +64,7 @@ private: class PeerListGlobalSearchController : public PeerListSearchController { public: - PeerListGlobalSearchController( - not_null navigation); + explicit PeerListGlobalSearchController(not_null session); void searchQuery(const QString &query) override; bool isLoading() override; @@ -79,7 +77,7 @@ private: void searchOnServer(); void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId); - const not_null _navigation; + const not_null _session; MTP::Sender _api; base::Timer _timer; QString _query; @@ -104,7 +102,7 @@ public: }; - ChatsListBoxController(not_null navigation); + ChatsListBoxController(not_null session); ChatsListBoxController( std::unique_ptr searchController); @@ -127,15 +125,15 @@ private: class ContactsBoxController : public PeerListController { public: + explicit ContactsBoxController(not_null session); ContactsBoxController( - not_null navigation); - ContactsBoxController( - not_null navigation, + not_null session, std::unique_ptr searchController); - Main::Session &session() const override; + [[nodiscard]] Main::Session &session() const override; void prepare() override final; - std::unique_ptr createSearchRow(not_null peer) override final; + [[nodiscard]] std::unique_ptr createSearchRow( + not_null peer) override final; void rowClicked(not_null row) override; protected: @@ -150,7 +148,7 @@ private: void checkForEmptyRows(); bool appendRow(not_null user); - const not_null _navigation; + const not_null _session; }; @@ -158,13 +156,9 @@ class AddBotToGroupBoxController : public ChatsListBoxController , public base::has_weak_ptr { public: - static void Start( - not_null navigation, - not_null bot); + static void Start(not_null bot); - AddBotToGroupBoxController( - not_null navigation, - not_null bot); + explicit AddBotToGroupBoxController(not_null bot); Main::Session &session() const override; void rowClicked(not_null row) override; @@ -186,7 +180,7 @@ private: void shareBotGame(not_null chat); void addBotToGroup(not_null chat); - not_null _bot; + const not_null _bot; }; @@ -195,7 +189,7 @@ class ChooseRecipientBoxController , public base::has_weak_ptr { public: ChooseRecipientBoxController( - not_null navigation, + not_null session, FnMut)> callback); Main::Session &session() const override; @@ -210,7 +204,7 @@ protected: std::unique_ptr createRow(not_null history) override; private: - const not_null _navigation; + const not_null _session; FnMut)> _callback; }; diff --git a/Telegram/SourceFiles/boxes/peer_lists_box.cpp b/Telegram/SourceFiles/boxes/peer_lists_box.cpp new file mode 100644 index 000000000..7ea2c91b4 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peer_lists_box.cpp @@ -0,0 +1,429 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/peer_lists_box.h" + +#include "lang/lang_keys.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/widgets/multi_select.h" +#include "ui/widgets/scroll_area.h" +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/data_peer.h" +#include "styles/style_boxes.h" +#include "styles/style_layers.h" + +PeerListsBox::PeerListsBox( + QWidget*, + std::vector> controllers, + Fn)> init) +: _lists(makeLists(std::move(controllers))) +, _init(std::move(init)) { + Expects(!_lists.empty()); +} + +auto PeerListsBox::collectSelectedRows() +-> std::vector> { + auto result = std::vector>(); + auto items = _select + ? _select->entity()->getItems() + : QVector(); + if (!items.empty()) { + result.reserve(items.size()); + const auto session = &firstController()->session(); + for (const auto itemId : items) { + const auto foreign = [&] { + for (const auto &list : _lists) { + if (list.controller->isForeignRow(itemId)) { + return true; + } + } + return false; + }(); + if (!foreign) { + result.push_back(session->data().peer(itemId)); + } + } + } + return result; +} + + +PeerListsBox::List PeerListsBox::makeList( + std::unique_ptr controller) { + auto delegate = std::make_unique(this, controller.get()); + return { + std::move(controller), + std::move(delegate), + }; +} + +std::vector PeerListsBox::makeLists( + std::vector> controllers) { + auto result = std::vector(); + result.reserve(controllers.size()); + for (auto &controller : controllers) { + result.push_back(makeList(std::move(controller))); + } + return result; +} + +not_null PeerListsBox::firstController() const { + return _lists.front().controller.get(); +} + +void PeerListsBox::createMultiSelect() { + Expects(_select == nullptr); + + auto entity = object_ptr( + this, + (firstController()->selectSt() + ? *firstController()->selectSt() + : st::defaultMultiSelect), + tr::lng_participant_filter()); + _select.create(this, std::move(entity)); + _select->heightValue( + ) | rpl::start_with_next( + [this] { updateScrollSkips(); }, + lifetime()); + _select->entity()->setSubmittedCallback([=](Qt::KeyboardModifiers) { + for (const auto &list : _lists) { + if (list.content->submitted()) { + break; + } + } + }); + _select->entity()->setQueryChangedCallback([=](const QString &query) { + searchQueryChanged(query); + }); + _select->entity()->setItemRemovedCallback([=](uint64 itemId) { + for (const auto &list : _lists) { + if (list.controller->handleDeselectForeignRow(itemId)) { + return; + } + } + const auto session = &firstController()->session(); + if (const auto peer = session->data().peerLoaded(itemId)) { + const auto id = peer->id; + for (const auto &list : _lists) { + if (const auto row = list.delegate->peerListFindRow(id)) { + list.content->changeCheckState( + row, + false, + anim::type::normal); + update(); + } + list.controller->itemDeselectedHook(peer); + } + } + }); + _select->resizeToWidth(firstController()->contentWidth()); + _select->moveToLeft(0, 0); +} + +int PeerListsBox::getTopScrollSkip() const { + auto result = 0; + if (_select && !_select->isHidden()) { + result += _select->height(); + } + return result; +} + +void PeerListsBox::updateScrollSkips() { + // If we show / hide the search field scroll top is fixed. + // If we resize search field by bubbles scroll bottom is fixed. + setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed); + if (!_select->animating()) { + _scrollBottomFixed = true; + } +} + +void PeerListsBox::prepare() { + auto rows = setInnerWidget( + object_ptr(this), + st::boxScroll); + for (auto &list : _lists) { + const auto content = rows->add(object_ptr( + rows, + list.controller.get())); + list.content = content; + list.delegate->setContent(content); + list.controller->setDelegate(list.delegate.get()); + + content->scrollToRequests( + ) | rpl::start_with_next([=](Ui::ScrollToRequest request) { + const auto skip = content->y(); + onScrollToY( + skip + request.ymin, + (request.ymax >= 0) ? (skip + request.ymax) : request.ymax); + }, lifetime()); + + content->selectedIndexValue( + ) | rpl::filter([=](int index) { + return (index >= 0); + }) | rpl::start_with_next([=] { + for (const auto &list : _lists) { + if (list.content && list.content != content) { + list.content->clearSelection(); + } + } + }, lifetime()); + } + rows->resizeToWidth(firstController()->contentWidth()); + + setDimensions(firstController()->contentWidth(), st::boxMaxListHeight); + if (_select) { + _select->finishAnimating(); + Ui::SendPendingMoveResizeEvents(_select); + _scrollBottomFixed = true; + onScrollToY(0); + } + + if (_init) { + _init(this); + } +} + +void PeerListsBox::keyPressEvent(QKeyEvent *e) { + const auto skipRows = [&](int rows) { + if (rows == 0) { + return; + } + for (const auto &list : _lists) { + if (list.content->hasPressed()) { + return; + } + } + const auto from = begin(_lists), till = end(_lists); + auto i = from; + for (; i != till; ++i) { + if (i->content->hasSelection()) { + break; + } + } + if (i == till && rows < 0) { + return; + } + if (rows > 0) { + if (i == till) { + i = from; + } + for (; i != till; ++i) { + const auto result = i->content->selectSkip(rows); + if (result.shouldMoveTo - result.reallyMovedTo >= rows) { + continue; + } else if (result.reallyMovedTo >= result.shouldMoveTo) { + return; + } else { + rows = result.shouldMoveTo - result.reallyMovedTo; + } + } + } else { + for (++i; i != from;) { + const auto result = (--i)->content->selectSkip(rows); + if (result.shouldMoveTo - result.reallyMovedTo <= rows) { + continue; + } else if (result.reallyMovedTo <= result.shouldMoveTo) { + return; + } else { + rows = result.shouldMoveTo - result.reallyMovedTo; + } + } + } + }; + const auto rowsInPage = [&] { + const auto rowHeight = firstController()->computeListSt().item.height; + return height() / rowHeight; + }; + if (e->key() == Qt::Key_Down) { + skipRows(1); + } else if (e->key() == Qt::Key_Up) { + skipRows(-1); + } else if (e->key() == Qt::Key_PageDown) { + skipRows(rowsInPage()); + } else if (e->key() == Qt::Key_PageUp) { + skipRows(-rowsInPage()); + } else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) { + _select->entity()->clearQuery(); + } else { + BoxContent::keyPressEvent(e); + } +} + +void PeerListsBox::searchQueryChanged(const QString &query) { + onScrollToY(0); + for (const auto &list : _lists) { + list.content->searchQueryChanged(query); + } +} + +void PeerListsBox::resizeEvent(QResizeEvent *e) { + BoxContent::resizeEvent(e); + + if (_select) { + _select->resizeToWidth(width()); + _select->moveToLeft(0, 0); + + updateScrollSkips(); + } + + for (const auto &list : _lists) { + list.content->resizeToWidth(width()); + } +} + +void PeerListsBox::paintEvent(QPaintEvent *e) { + Painter p(this); + + const auto &bg = (firstController()->listSt() + ? *firstController()->listSt() + : st::peerListBox).bg; + for (const auto rect : e->region()) { + p.fillRect(rect, bg); + } +} + +void PeerListsBox::setInnerFocus() { + if (!_select || !_select->toggled()) { + _lists.front().content->setFocus(); + } else { + _select->entity()->setInnerFocus(); + } +} + +PeerListsBox::Delegate::Delegate( + not_null box, + not_null controller) +: _box(box) +, _controller(controller) { +} + +void PeerListsBox::Delegate::peerListSetTitle(rpl::producer title) { +} + +void PeerListsBox::Delegate::peerListSetAdditionalTitle( + rpl::producer title) { +} + +void PeerListsBox::Delegate::peerListSetRowChecked( + not_null row, + bool checked) { + if (checked) { + _box->addSelectItem(row, anim::type::normal); + PeerListContentDelegate::peerListSetRowChecked(row, checked); + peerListUpdateRow(row); + + // This call deletes row from _searchRows. + _box->_select->entity()->clearQuery(); + } else { + // The itemRemovedCallback will call changeCheckState() here. + _box->_select->entity()->removeItem(row->id()); + peerListUpdateRow(row); + } +} + +void PeerListsBox::Delegate::peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) { + if (checked) { + _box->addSelectItem(row, animated); + + // This call deletes row from _searchRows. + _box->_select->entity()->clearQuery(); + } else { + // The itemRemovedCallback will call changeCheckState() here. + _box->_select->entity()->removeItem(row->id()); + } +} + +void PeerListsBox::Delegate::peerListScrollToTop() { + _box->onScrollToY(0); +} + +void PeerListsBox::Delegate::peerListSetSearchMode(PeerListSearchMode mode) { + PeerListContentDelegate::peerListSetSearchMode(mode); + _box->setSearchMode(mode); +} + +void PeerListsBox::setSearchMode(PeerListSearchMode mode) { + auto selectVisible = (mode != PeerListSearchMode::Disabled); + if (selectVisible && !_select) { + createMultiSelect(); + _select->toggle(!selectVisible, anim::type::instant); + } + if (_select) { + _select->toggle(selectVisible, anim::type::normal); + _scrollBottomFixed = false; + setInnerFocus(); + } +} + +void PeerListsBox::Delegate::peerListFinishSelectedRowsBunch() { + Expects(_box->_select != nullptr); + + _box->_select->entity()->finishItemsBunch(); +} + +bool PeerListsBox::Delegate::peerListIsRowChecked( + not_null row) { + return _box->_select + ? _box->_select->entity()->hasItem(row->id()) + : false; +} + +int PeerListsBox::Delegate::peerListSelectedRowsCount() { + return _box->_select ? _box->_select->entity()->getItemsCount() : 0; +} + +void PeerListsBox::addSelectItem( + not_null peer, + anim::type animated) { + addSelectItem( + peer->id, + peer->shortName(), + PaintUserpicCallback(peer, false), + animated); +} + +void PeerListsBox::addSelectItem( + not_null row, + anim::type animated) { + addSelectItem( + row->id(), + row->generateShortName(), + row->generatePaintUserpicCallback(), + animated); +} + +void PeerListsBox::addSelectItem( + uint64 itemId, + const QString &text, + Ui::MultiSelect::PaintRoundImage paintUserpic, + anim::type animated) { + if (!_select) { + createMultiSelect(); + _select->hide(anim::type::instant); + } + const auto &activeBg = (firstController()->selectSt() + ? *firstController()->selectSt() + : st::defaultMultiSelect).item.textActiveBg; + if (animated == anim::type::instant) { + _select->entity()->addItemInBunch( + itemId, + text, + activeBg, + std::move(paintUserpic)); + } else { + _select->entity()->addItem( + itemId, + text, + activeBg, + std::move(paintUserpic)); + } +} diff --git a/Telegram/SourceFiles/boxes/peer_lists_box.h b/Telegram/SourceFiles/boxes/peer_lists_box.h new file mode 100644 index 000000000..09e8e92e2 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peer_lists_box.h @@ -0,0 +1,101 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "boxes/peer_list_box.h" + +class PeerListsBox : public Ui::BoxContent { +public: + PeerListsBox( + QWidget*, + std::vector> controllers, + Fn)> init); + + [[nodiscard]] std::vector> collectSelectedRows(); + +protected: + void prepare() override; + void setInnerFocus() override; + + void keyPressEvent(QKeyEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + +private: + class Delegate final : public PeerListContentDelegate { + public: + Delegate( + not_null box, + not_null controller); + + void peerListSetTitle(rpl::producer title) override; + void peerListSetAdditionalTitle(rpl::producer title) override; + void peerListSetSearchMode(PeerListSearchMode mode) override; + void peerListSetRowChecked( + not_null row, + bool checked) override; + void peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) override; + bool peerListIsRowChecked(not_null row) override; + int peerListSelectedRowsCount() override; + void peerListScrollToTop() override; + + void peerListAddSelectedPeerInBunch(not_null peer) override { + _box->addSelectItem(peer, anim::type::instant); + } + void peerListAddSelectedRowInBunch(not_null row) override { + _box->addSelectItem(row, anim::type::instant); + } + void peerListFinishSelectedRowsBunch() override; + + private: + const not_null _box; + const not_null _controller; + + }; + struct List { + std::unique_ptr controller; + std::unique_ptr delegate; + PeerListContent *content = nullptr; + }; + + friend class Delegate; + + [[nodiscard]] List makeList( + std::unique_ptr controller); + [[nodiscard]] std::vector makeLists( + std::vector> controllers); + + [[nodiscard]] not_null firstController() const; + + void addSelectItem( + not_null peer, + anim::type animated); + void addSelectItem( + not_null row, + anim::type animated); + void addSelectItem( + uint64 itemId, + const QString &text, + PaintRoundImageCallback paintUserpic, + anim::type animated); + void setSearchMode(PeerListSearchMode mode); + void createMultiSelect(); + int getTopScrollSkip() const; + void updateScrollSkips(); + void searchQueryChanged(const QString &query); + + object_ptr> _select = { nullptr }; + + std::vector _lists; + Fn _init; + bool _scrollBottomFixed = false; + +}; diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index dda179e00..9ac503df3 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -51,28 +51,21 @@ base::flat_set> GetAlreadyInFromPeer(PeerData *peer) { } // namespace AddParticipantsBoxController::AddParticipantsBoxController( - not_null navigation) -: ContactsBoxController( - navigation, - std::make_unique(navigation)) { + not_null session) +: ContactsBoxController(session) { } AddParticipantsBoxController::AddParticipantsBoxController( - not_null navigation, not_null peer) : AddParticipantsBoxController( - navigation, peer, GetAlreadyInFromPeer(peer)) { } AddParticipantsBoxController::AddParticipantsBoxController( - not_null navigation, not_null peer, base::flat_set> &&alreadyIn) -: ContactsBoxController( - navigation, - std::make_unique(navigation)) +: ContactsBoxController(&peer->session()) , _peer(peer) , _alreadyIn(std::move(alreadyIn)) { subscribeToMigration(); @@ -179,7 +172,7 @@ bool AddParticipantsBoxController::inviteSelectedUsers( not_null box) const { Expects(_peer != nullptr); - const auto rows = box->peerListCollectSelectedRows(); + const auto rows = box->collectSelectedRows(); const auto users = ranges::view::all( rows ) | ranges::view::transform([](not_null peer) { @@ -198,9 +191,7 @@ bool AddParticipantsBoxController::inviteSelectedUsers( void AddParticipantsBoxController::Start( not_null navigation, not_null chat) { - auto controller = std::make_unique( - navigation, - chat); + auto controller = std::make_unique(chat); const auto weak = controller.get(); auto initBox = [=](not_null box) { box->addButton(tr::lng_participant_invite(), [=] { @@ -223,7 +214,6 @@ void AddParticipantsBoxController::Start( base::flat_set> &&alreadyIn, bool justCreated) { auto controller = std::make_unique( - navigation, channel, std::move(alreadyIn)); const auto weak = controller.get(); diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.h b/Telegram/SourceFiles/boxes/peers/add_participants_box.h index 122491c6d..7bba41119 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.h +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.h @@ -27,16 +27,16 @@ public: not_null channel, base::flat_set> &&alreadyIn); - explicit AddParticipantsBoxController( - not_null navigation); + explicit AddParticipantsBoxController(not_null session); + explicit AddParticipantsBoxController(not_null peer); AddParticipantsBoxController( - not_null navigation, - not_null peer); - AddParticipantsBoxController( - not_null navigation, not_null peer, base::flat_set> &&alreadyIn); + [[nodiscard]] not_null peer() const { + return _peer; + } + void rowClicked(not_null row) override; void itemDeselectedHook(not_null peer) override; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 0fb1facc7..d1921073f 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -477,11 +477,17 @@ groupCallMembersListItem: PeerListItem(defaultPeerListItem) { groupCallMembersList: PeerList(defaultPeerList) { bg: groupCallMembersBg; about: FlatLabel(defaultPeerListAbout) { - textFg: groupCallMemberInactiveStatus; + textFg: groupCallMemberNotJoinedStatus; } item: groupCallMembersListItem; } +groupCallInviteDividerLabel: FlatLabel(defaultFlatLabel) { + textFg: groupCallMemberNotJoinedStatus; +} +groupCallInviteDividerPadding: margins(17px, 7px, 17px, 7px); + groupCallInviteMembersList: PeerList(groupCallMembersList) { + padding: margins(0px, 10px, 0px, 10px); item: PeerListItem(groupCallMembersListItem) { statusFg: groupCallMemberNotJoinedStatus; statusFgOver: groupCallMemberNotJoinedStatus; diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index e343454de..089bb5c50 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -1410,6 +1410,9 @@ void GroupMembers::peerListSetTitle(rpl::producer title) { void GroupMembers::peerListSetAdditionalTitle(rpl::producer title) { } +void GroupMembers::peerListSetHideEmpty(bool hide) { +} + bool GroupMembers::peerListIsRowChecked(not_null row) { return false; } @@ -1421,10 +1424,6 @@ int GroupMembers::peerListSelectedRowsCount() { return 0; } -std::vector> GroupMembers::peerListCollectSelectedRows() { - return {}; -} - void GroupMembers::peerListAddSelectedPeerInBunch(not_null peer) { Unexpected("Item selection in Calls::GroupMembers."); } diff --git a/Telegram/SourceFiles/calls/calls_group_members.h b/Telegram/SourceFiles/calls/calls_group_members.h index 6216e35f7..e255d2ee4 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.h +++ b/Telegram/SourceFiles/calls/calls_group_members.h @@ -57,10 +57,10 @@ private: // PeerListContentDelegate interface. void peerListSetTitle(rpl::producer title) override; void peerListSetAdditionalTitle(rpl::producer title) override; + void peerListSetHideEmpty(bool hide) override; bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; void peerListScrollToTop() override; - std::vector> peerListCollectSelectedRows() override; void peerListAddSelectedPeerInBunch( not_null peer) override; void peerListAddSelectedRowInBunch( diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index 92f1f6bc6..0665318b1 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -29,6 +29,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "base/event_filter.h" #include "boxes/peers/edit_participants_box.h" +#include "boxes/peers/add_participants_box.h" +#include "boxes/peer_lists_box.h" +#include "boxes/confirm_box.h" #include "app.h" #include "apiwrap.h" // api().kickParticipant. #include "styles/style_calls.h" @@ -51,8 +54,7 @@ class InviteController final : public ParticipantsBoxController { public: InviteController( not_null peer, - base::flat_set> alreadyIn, - int fullInCount); + base::flat_set> alreadyIn); void prepare() override; @@ -63,34 +65,81 @@ public: void itemDeselectedHook(not_null peer) override; - std::variant> inviteSelectedUsers( - not_null box, - not_null call) const; + [[nodiscard]] auto peersWithRows() const + -> not_null>*>; + [[nodiscard]] rpl::producer> rowAdded() const; + + [[nodiscard]] bool hasRowFor(not_null peer) const; private: - [[nodiscard]] int alreadyInCount() const; [[nodiscard]] bool isAlreadyIn(not_null user) const; - [[nodiscard]] int fullCount() const; std::unique_ptr createRow( not_null user) const override; not_null _peer; const base::flat_set> _alreadyIn; - const int _fullInCount = 0; - mutable base::flat_set> _skippedUsers; + mutable base::flat_set> _inGroup; + rpl::event_stream> _rowAdded; }; +class InviteContactsController final : public AddParticipantsBoxController { +public: + InviteContactsController( + not_null peer, + base::flat_set> alreadyIn, + not_null>*> inGroup, + rpl::producer> discoveredInGroup); + +private: + void prepareViewHook() override; + + std::unique_ptr createRow( + not_null user) override; + + const not_null>*> _inGroup; + rpl::producer> _discoveredInGroup; + + rpl::lifetime _lifetime; + +}; + +[[nodiscard]] object_ptr CreateSectionSubtitle( + QWidget *parent, + rpl::producer text) { + auto result = object_ptr( + parent, + st::searchedBarHeight); + + const auto raw = result.data(); + raw->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(raw); + p.fillRect(clip, st::groupCallMembersBgOver); + }, raw->lifetime()); + + const auto label = Ui::CreateChild( + raw, + std::move(text), + st::groupCallBoxLabel); + raw->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto padding = st::groupCallInviteDividerPadding; + const auto available = width - padding.left() - padding.right(); + label->resizeToNaturalWidth(available); + label->moveToLeft(padding.left(), padding.top(), width); + }, label->lifetime()); + + return result; +} + InviteController::InviteController( not_null peer, - base::flat_set> alreadyIn, - int fullInCount) + base::flat_set> alreadyIn) : ParticipantsBoxController(CreateTag{}, nullptr, peer, Role::Members) , _peer(peer) -, _alreadyIn(std::move(alreadyIn)) -, _fullInCount(std::max(fullInCount, int(_alreadyIn.size()))) { - _skippedUsers.emplace(peer->session().user()); +, _alreadyIn(std::move(alreadyIn)) { SubscribeToMigration( _peer, lifetime(), @@ -98,8 +147,14 @@ InviteController::InviteController( } void InviteController::prepare() { + delegate()->peerListSetHideEmpty(true); ParticipantsBoxController::prepare(); - delegate()->peerListSetTitle(tr::lng_group_call_invite_title()); + delegate()->peerListSetAboveWidget(CreateSectionSubtitle( + nullptr, + tr::lng_group_call_invite_members())); + delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle( + nullptr, + tr::lng_group_call_invite_members())); } void InviteController::rowClicked(not_null row) { @@ -115,44 +170,71 @@ base::unique_qptr InviteController::rowContextMenu( void InviteController::itemDeselectedHook(not_null peer) { } -int InviteController::alreadyInCount() const { - return std::max(_fullInCount, int(_alreadyIn.size())); +bool InviteController::hasRowFor(not_null peer) const { + return (delegate()->peerListFindRow(peer->id) != nullptr); } bool InviteController::isAlreadyIn(not_null user) const { return _alreadyIn.contains(user); } -int InviteController::fullCount() const { - return alreadyInCount() + delegate()->peerListSelectedRowsCount(); -} - std::unique_ptr InviteController::createRow( not_null user) const { if (user->isSelf() || user->isBot()) { - _skippedUsers.emplace(user); return nullptr; } auto result = std::make_unique(user); + _rowAdded.fire_copy(user); + _inGroup.emplace(user); if (isAlreadyIn(user)) { result->setDisabledState(PeerListRow::State::DisabledChecked); } return result; } -std::variant> InviteController::inviteSelectedUsers( - not_null box, - not_null call) const { - const auto rows = box->peerListCollectSelectedRows(); - const auto users = ranges::view::all( - rows - ) | ranges::view::transform([](not_null peer) { - Expects(peer->isUser()); - Expects(!peer->isSelf()); +auto InviteController::peersWithRows() const +-> not_null>*> { + return &_inGroup; +} - return not_null(peer->asUser()); - }) | ranges::to_vector; - return call->inviteUsers(users); +rpl::producer> InviteController::rowAdded() const { + return _rowAdded.events(); +} + +InviteContactsController::InviteContactsController( + not_null peer, + base::flat_set> alreadyIn, + not_null>*> inGroup, + rpl::producer> discoveredInGroup) +: AddParticipantsBoxController(peer, std::move(alreadyIn)) +, _inGroup(inGroup) +, _discoveredInGroup(std::move(discoveredInGroup)) { +} + +void InviteContactsController::prepareViewHook() { + AddParticipantsBoxController::prepareViewHook(); + + delegate()->peerListSetAboveWidget(CreateSectionSubtitle( + nullptr, + tr::lng_contacts_header())); + delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle( + nullptr, + tr::lng_group_call_invite_search_results())); + + std::move( + _discoveredInGroup + ) | rpl::start_with_next([=](not_null user) { + if (auto row = delegate()->peerListFindRow(user->id)) { + delegate()->peerListRemoveRow(row); + } + }, _lifetime); +} + +std::unique_ptr InviteContactsController::createRow( + not_null user) { + return _inGroup->contains(user) + ? nullptr + : AddParticipantsBoxController::createRow(user); } } // namespace @@ -197,6 +279,21 @@ void LeaveGroupCallBox( box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } +void GroupCallConfirmBox( + not_null box, + const QString &text, + rpl::producer button, + Fn callback) { + box->addRow( + object_ptr( + box.get(), + text, + st::groupCallBoxLabel), + st::boxPadding); + box->addButton(std::move(button), callback); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + GroupPanel::GroupPanel(not_null call) : _call(call) , _peer(call->peer()) @@ -362,7 +459,7 @@ void GroupPanel::initControls() { }); _settings->setText(tr::lng_menu_settings()); - _hangup->setText(tr::lng_box_leave()); + _hangup->setText(tr::lng_group_call_leave()); _members->desiredHeightValue( ) | rpl::start_with_next([=] { @@ -460,52 +557,131 @@ void GroupPanel::addMembers() { alreadyIn.emplace(_peer->session().user()); auto controller = std::make_unique( _peer, - std::move(alreadyIn), - real->fullCount()); + alreadyIn); controller->setStyleOverrides( &st::groupCallInviteMembersList, &st::groupCallMultiSelect); - const auto weak = base::make_weak(_call); - auto initBox = [=, controller = controller.get()]( - not_null box) { - box->addButton(tr::lng_group_call_invite_button(), [=] { - if (const auto call = weak.get()) { - const auto result = controller->inviteSelectedUsers(box, call); + auto contactsController = std::make_unique( + _peer, + std::move(alreadyIn), + controller->peersWithRows(), + controller->rowAdded()); + contactsController->setStyleOverrides( + &st::groupCallInviteMembersList, + &st::groupCallMultiSelect); - if (const auto user = std::get_if>(&result)) { - Ui::Toast::Show( - widget(), - Ui::Toast::Config{ - .text = tr::lng_group_call_invite_done_user( - tr::now, - lt_user, - Ui::Text::Bold((*user)->firstName), - Ui::Text::WithEntities), - .st = &st::defaultToast, - }); - } else if (const auto count = std::get_if(&result)) { - if (*count > 0) { - Ui::Toast::Show( - widget(), - Ui::Toast::Config{ - .text = tr::lng_group_call_invite_done_many( - tr::now, - lt_count, - *count, - Ui::Text::RichLangValue), - .st = &st::defaultToast, - }); - } - } else { - Unexpected("Result in GroupCall::inviteUsers."); - } + const auto weak = base::make_weak(_call); + const auto invite = [=](const std::vector> &users) { + const auto call = weak.get(); + if (!call) { + return; + } + const auto result = call->inviteUsers(users); + if (const auto user = std::get_if>(&result)) { + Ui::Toast::Show( + widget(), + Ui::Toast::Config{ + .text = tr::lng_group_call_invite_done_user( + tr::now, + lt_user, + Ui::Text::Bold((*user)->firstName), + Ui::Text::WithEntities), + .st = &st::defaultToast, + }); + } else if (const auto count = std::get_if(&result)) { + if (*count > 0) { + Ui::Toast::Show( + widget(), + Ui::Toast::Config{ + .text = tr::lng_group_call_invite_done_many( + tr::now, + lt_count, + *count, + Ui::Text::RichLangValue), + .st = &st::defaultToast, + }); } - box->closeBox(); + } else { + Unexpected("Result in GroupCall::inviteUsers."); + } + }; + const auto inviteWithAdd = [=]( + const std::vector> &users, + const std::vector> &nonMembers, + Fn finish) { + _peer->session().api().addChatParticipants( + _peer, + nonMembers, + [=](bool) { invite(users); finish(); }); + }; + const auto inviteWithConfirmation = [=]( + const std::vector> &users, + const std::vector> &nonMembers, + Fn finish) { + if (nonMembers.empty()) { + invite(users); + finish(); + return; + } + const auto name = _peer->name; + const auto text = (nonMembers.size() == 1) + ? tr::lng_group_call_add_to_group_one( + tr::now, + lt_user, + nonMembers.front()->shortName(), + lt_group, + name) + : (nonMembers.size() < users.size()) + ? tr::lng_group_call_add_to_group_some(tr::now, lt_group, name) + : tr::lng_group_call_add_to_group_all(tr::now, lt_group, name); + const auto shared = std::make_shared>(); + const auto finishWithConfirm = [=] { + if (*shared) { + (*shared)->closeBox(); + } + finish(); + }; + auto box = Box( + GroupCallConfirmBox, + text, + tr::lng_participant_invite(), + [=] { inviteWithAdd(users, nonMembers, finishWithConfirm); }); + *shared = box.data(); + _layerBg->showBox(std::move(box)); + }; + auto initBox = [=, controller = controller.get()]( + not_null box) { + box->setTitle(tr::lng_group_call_invite_title()); + box->addButton(tr::lng_group_call_invite_button(), [=] { + const auto rows = box->collectSelectedRows(); + + const auto users = ranges::view::all( + rows + ) | ranges::view::transform([](not_null peer) { + return not_null(peer->asUser()); + }) | ranges::to_vector; + + const auto nonMembers = ranges::view::all( + users + ) | ranges::view::filter([&](not_null user) { + return !controller->hasRowFor(user); + }) | ranges::to_vector; + + const auto finish = [box = Ui::MakeWeak(box)]() { + if (box) { + box->closeBox(); + } + }; + inviteWithConfirmation(users, nonMembers, finish); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); }; - _layerBg->showBox(Box(std::move(controller), initBox)); + + auto controllers = std::vector>(); + controllers.push_back(std::move(controller)); + controllers.push_back(std::move(contactsController)); + _layerBg->showBox(Box(std::move(controllers), initBox)); } void GroupPanel::kickMember(not_null user) { diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index ed4fa9618..f74c8007b 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -33,7 +33,7 @@ GroupCall::GroupCall( uint64 accessHash) : _id(id) , _accessHash(accessHash) -, _peer(peer) // #TODO calls migration +, _peer(peer) , _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) { } diff --git a/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.cpp b/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.cpp index fca98f1b6..7ddf3165e 100644 --- a/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.cpp +++ b/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.cpp @@ -258,10 +258,6 @@ int InnerWidget::peerListSelectedRowsCount() { return 0; } -std::vector> InnerWidget::peerListCollectSelectedRows() { - return {}; -} - void InnerWidget::peerListScrollToTop() { _scrollToRequests.fire({ -1, -1 }); } diff --git a/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.h b/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.h index f6f731400..3ad4b007d 100644 --- a/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.h +++ b/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.h @@ -52,7 +52,6 @@ private: void peerListSetAdditionalTitle(rpl::producer title) override; bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; - std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; void peerListAddSelectedPeerInBunch( not_null peer) override; diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp index 8f1a52fd6..48017e7a1 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp +++ b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp @@ -57,7 +57,6 @@ public: void peerListSetAdditionalTitle(rpl::producer title) override; bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; - std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; void peerListAddSelectedPeerInBunch( not_null peer) override; @@ -130,11 +129,6 @@ int ListDelegate::peerListSelectedRowsCount() { return 0; } -auto ListDelegate::peerListCollectSelectedRows() --> std::vector> { - return {}; -} - void ListDelegate::peerListScrollToTop() { } diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index ba4c19842..f9ddf2910 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -493,7 +493,7 @@ void ActionsFiller::addInviteToGroupAction( _wrap, tr::lng_profile_invite_to_group(), CanInviteBotToGroupValue(user), - [=] { AddBotToGroupBoxController::Start(controller, user); }); + [=] { AddBotToGroupBoxController::Start(user); }); } void ActionsFiller::addShareContactAction(not_null user) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.cpp b/Telegram/SourceFiles/info/profile/info_profile_members.cpp index f071ab4e3..f3621ca6b 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_members.cpp @@ -423,10 +423,6 @@ int Members::peerListSelectedRowsCount() { return 0; } -std::vector> Members::peerListCollectSelectedRows() { - return {}; -} - void Members::peerListScrollToTop() { _scrollToRequests.fire({ -1, -1 }); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.h b/Telegram/SourceFiles/info/profile/info_profile_members.h index cdfba90ac..bf7c6332a 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members.h +++ b/Telegram/SourceFiles/info/profile/info_profile_members.h @@ -63,7 +63,6 @@ private: void peerListSetAdditionalTitle(rpl::producer title) override; bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; - std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; void peerListAddSelectedPeerInBunch( not_null peer) override; diff --git a/Telegram/SourceFiles/platform/win/windows_dlls.cpp b/Telegram/SourceFiles/platform/win/windows_dlls.cpp index bac70f738..01301f702 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.cpp +++ b/Telegram/SourceFiles/platform/win/windows_dlls.cpp @@ -42,6 +42,7 @@ void init() { u"rstrtmgr.dll"_q, u"psapi.dll"_q, u"user32.dll"_q, + u"thai.dll"_q, }; for (const auto &lib : list) { SafeLoadLibrary(lib); diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 466f1bc9a..efb774bcf 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -51,8 +51,7 @@ class BlockPeerBoxController : public ChatsListBoxController , private base::Subscriber { public: - explicit BlockPeerBoxController( - not_null navigation); + explicit BlockPeerBoxController(not_null session); Main::Session &session() const override; void rowClicked(not_null row) override; @@ -72,19 +71,19 @@ protected: private: void updateIsBlocked(not_null row, PeerData *peer) const; - const not_null _navigation; + const not_null _session; Fn peer)> _blockPeerCallback; }; BlockPeerBoxController::BlockPeerBoxController( - not_null navigation) -: ChatsListBoxController(navigation) -, _navigation(navigation) { + not_null session) +: ChatsListBoxController(session) +, _session(session) { } Main::Session &BlockPeerBoxController::session() const { - return _navigation->session(); + return *_session; } void BlockPeerBoxController::prepareViewHook() { @@ -294,7 +293,8 @@ void BlockedBoxController::handleBlockedEvent(not_null user) { void BlockedBoxController::BlockNewPeer( not_null window) { - auto controller = std::make_unique(window); + auto controller = std::make_unique( + &window->session()); auto initBox = [=, controller = controller.get()]( not_null box) { controller->setBlockPeerCallback([=](not_null peer) { diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 3e8a06744..5caebf94c 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -483,7 +483,7 @@ void Filler::addUserActions(not_null user) { using AddBotToGroup = AddBotToGroupBoxController; _addAction( tr::lng_profile_invite_to_group(tr::now), - [=] { AddBotToGroup::Start(controller, user); }); + [=] { AddBotToGroup::Start(user); }); } addPollAction(user); if (user->canExportChatHistory()) { @@ -803,7 +803,7 @@ void PeerMenuShareContactBox( }; *weak = Ui::show(Box( std::make_unique( - navigation, + &navigation->session(), std::move(callback)), [](not_null box) { box->addButton(tr::lng_cancel(), [=] { @@ -1010,7 +1010,7 @@ QPointer ShowForwardMessagesBox( }; *weak = Ui::show(Box( std::make_unique( - navigation, + &navigation->session(), std::move(callback)), std::move(initBox)), Ui::LayerOption::KeepOther); return weak->data(); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index a4063bd14..148db3026 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -194,7 +194,7 @@ void SessionNavigation::showPeerByLinkResolved( const auto user = peer->asUser(); if (user && user->isBot() && !info.startToken.isEmpty()) { user->botInfo->shareGameShortName = info.startToken; - AddBotToGroupBoxController::Start(this, user); + AddBotToGroupBoxController::Start(user); } else { crl::on_main(this, [=] { showPeerHistory(peer->id, params); @@ -207,7 +207,7 @@ void SessionNavigation::showPeerByLinkResolved( && !user->botInfo->cantJoinGroups && !info.startToken.isEmpty()) { user->botInfo->startGroupToken = info.startToken; - AddBotToGroupBoxController::Start(this, user); + AddBotToGroupBoxController::Start(user); } else if (user && user->isBot()) { // Always open bot chats, even from mention links. crl::on_main(this, [=] {