diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c2c5c9c88..09c817c31 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -640,7 +640,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_report_button" = "Report"; "lng_report_thanks" = "Thank you! Your report will be reviewed by our team very soon."; -"lng_channel_add_admins" = "New administrator"; "lng_channel_add_members" = "Add members"; "lng_channel_add_banned" = "Ban user"; "lng_channel_add_restricted" = "Restrict user"; @@ -652,6 +651,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_channel_admins_too_much" = "Sorry, you have reached the limit of the administrators. Please remove one administrator first."; "lng_channel_admin_status_creator" = "Creator"; "lng_channel_admin_status_promoted_by" = "Promoted by {user}"; +"lng_channel_admin_status_not_admin" = "Not administrator"; "lng_group_blocked_list_about" = "Banned users are removed from the group and can only come back if invited by an admin.\nInvite links don't work for them."; @@ -1405,6 +1405,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_topbar_info" = "Info"; "lng_profile_group_info" = "Group info"; "lng_profile_channel_info" = "Channel info"; +"lng_channel_add_admins" = "New administrator"; // Wnd specific diff --git a/Telegram/SourceFiles/boxes/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/edit_participant_box.cpp index 6666f9e64..d479e5511 100644 --- a/Telegram/SourceFiles/boxes/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_participant_box.cpp @@ -233,12 +233,13 @@ MTPChannelAdminRights EditAdminBox::DefaultRights(gsl::not_null ch void EditAdminBox::prepare() { EditParticipantBox::prepare(); - setTitle(langFactory(lng_rights_edit_admin)); + auto hadRights = _oldRights.c_channelAdminRights().vflags.v; + setTitle(langFactory(hadRights ? lng_rights_edit_admin : lng_channel_add_admin)); addControl(object_ptr(this), QMargins()); addControl(object_ptr(this, lang(lng_rights_edit_admin_header), Ui::FlatLabel::InitType::Simple, st::rightsHeaderLabel), st::rightsHeaderMargin); - auto prepareRights = (_oldRights.c_channelAdminRights().vflags.v ? _oldRights : DefaultRights(channel())); + auto prepareRights = (hadRights ? _oldRights : DefaultRights(channel())); auto addCheckbox = [this, &prepareRights](Flags flags, const QString &text) { auto checked = (prepareRights.c_channelAdminRights().vflags.v & flags) != 0; auto control = addControl(object_ptr(this, text, checked, st::rightsCheckbox, st::rightsToggle), st::rightsToggleMargin); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 771d9429e..fa022d40c 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -145,6 +145,10 @@ void PeerListBox::peerListPrependRow(std::unique_ptr row) { _inner->prependRow(std::move(row)); } +void PeerListBox::peerListPrependRowFromSearchResult(gsl::not_null row) { + _inner->prependRowFromSearchResult(row); +} + PeerListRow *PeerListBox::peerListFindRow(PeerListRowId id) { return _inner->findRow(id); } @@ -157,6 +161,10 @@ void PeerListBox::peerListRemoveRow(gsl::not_null row) { _inner->removeRow(row); } +void PeerListBox::peerListConvertRowToSearchResult(gsl::not_null row) { + _inner->convertRowToSearchResult(row); +} + void PeerListBox::peerListSetRowChecked(gsl::not_null row, bool checked) { auto peer = row->peer(); if (checked) { @@ -203,7 +211,7 @@ void PeerListBox::peerListSetSearchNoResults(object_ptr noResults void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) { _inner->setSearchMode(mode); - if (mode != PeerListSearchMode::None && !_select) { + if (mode != PeerListSearchMode::Disabled && !_select) { _select = createMultiSelect(); _select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { _inner->submitted(); }); _select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); }); @@ -219,7 +227,7 @@ void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) { _select->moveToLeft(0, 0); } if (_select) { - _select->toggleAnimated(mode != PeerListSearchMode::None); + _select->toggleAnimated(mode != PeerListSearchMode::Disabled); } } @@ -245,8 +253,12 @@ PeerListController::PeerListController(std::unique_ptr } } +bool PeerListController::hasComplexSearch() const { + return (_searchController != nullptr); +} + void PeerListController::search(const QString &query) { - Expects(_searchController != nullptr); + Expects(hasComplexSearch()); _searchController->searchQuery(query); } @@ -534,7 +546,7 @@ void PeerListBox::Inner::addRowEntry(gsl::not_null row) { if (addingToSearchIndex()) { addToSearchIndex(row); } - if (_searchMode != PeerListSearchMode::None) { + if (_searchMode != PeerListSearchMode::Disabled) { t_assert(row->id() == row->peer()->id); if (_controller->isRowSelected(row->peer())) { changeCheckState(row, true, PeerListRow::SetStyle::Fast); @@ -553,7 +565,7 @@ void PeerListBox::Inner::invalidatePixmapsCache() { bool PeerListBox::Inner::addingToSearchIndex() const { // If we started indexing already, we continue. - return (_searchMode != PeerListSearchMode::None) || !_searchIndex.empty(); + return (_searchMode != PeerListSearchMode::Disabled) || !_searchIndex.empty(); } void PeerListBox::Inner::addToSearchIndex(gsl::not_null row) { @@ -594,6 +606,25 @@ void PeerListBox::Inner::prependRow(std::unique_ptr row) { } } +void PeerListBox::Inner::prependRowFromSearchResult(gsl::not_null row) { + if (!row->isSearchResult()) { + return; + } + t_assert(_rowsById.find(row->id()) != _rowsById.cend()); + auto index = row->absoluteIndex(); + t_assert(index >= 0 && index < _searchRows.size()); + t_assert(_searchRows[index].get() == row); + + row->setIsSearchResult(false); + _rows.insert(_rows.begin(), std::move(_searchRows[index])); + refreshIndices(); + removeRowAtIndex(_searchRows, index); + + if (addingToSearchIndex()) { + addToSearchIndex(row); + } +} + void PeerListBox::Inner::refreshIndices() { auto index = 0; for (auto &row : _rows) { @@ -601,6 +632,13 @@ void PeerListBox::Inner::refreshIndices() { } } +void PeerListBox::Inner::removeRowAtIndex(std::vector> &from, int index) { + from.erase(from.begin() + index); + for (auto i = index, count = int(from.size()); i != count; ++i) { + from[i]->setAbsoluteIndex(i); + } +} + PeerListRow *PeerListBox::Inner::findRow(PeerListRowId id) { auto it = _rowsById.find(id); return (it == _rowsById.cend()) ? nullptr : it->second.get(); @@ -622,14 +660,28 @@ void PeerListBox::Inner::removeRow(gsl::not_null row) { byPeer.erase(std::remove(byPeer.begin(), byPeer.end(), row), byPeer.end()); removeFromSearchIndex(row); _filterResults.erase(std::find(_filterResults.begin(), _filterResults.end(), row), _filterResults.end()); - eraseFrom.erase(eraseFrom.begin() + index); - for (auto i = index, count = int(eraseFrom.size()); i != count; ++i) { - eraseFrom[i]->setAbsoluteIndex(i); - } + removeRowAtIndex(eraseFrom, index); restoreSelection(); } +void PeerListBox::Inner::convertRowToSearchResult(gsl::not_null row) { + if (row->isSearchResult()) { + return; + } else if (!showingSearch() || !_controller->hasComplexSearch()) { + return removeRow(row); + } + auto index = row->absoluteIndex(); + t_assert(index >= 0 && index < _rows.size()); + t_assert(_rows[index].get() == row); + + removeFromSearchIndex(row); + row->setIsSearchResult(true); + row->setAbsoluteIndex(_searchRows.size()); + _searchRows.push_back(std::move(_rows[index])); + removeRowAtIndex(_rows, index); +} + int PeerListBox::Inner::fullRowsCount() const { return _rows.size(); } @@ -709,7 +761,7 @@ void PeerListBox::Inner::setSearchMode(PeerListSearchMode mode) { } } _searchMode = mode; - if (_searchMode == PeerListSearchMode::Complex) { + if (_controller->hasComplexSearch()) { if (!_searchLoading) { setSearchLoading(object_ptr(this, lang(lng_contacts_loading), Ui::FlatLabel::InitType::Simple, st::membersAbout)); } @@ -1027,7 +1079,7 @@ void PeerListBox::Inner::searchQueryChanged(QString query) { } } } - if (_searchMode == PeerListSearchMode::Complex) { + if (_controller->hasComplexSearch()) { _controller->search(_searchQuery); } refreshRows(); @@ -1314,7 +1366,7 @@ ChatsListBoxController::ChatsListBoxController(std::unique_ptrpeerListSetSearchMode(PeerListSearchMode::Complex); + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); prepareViewHook(); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index a9e7153e7..9d158bd47 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -163,9 +163,8 @@ private: }; enum class PeerListSearchMode { - None, - Local, - Complex, + Disabled, + Enabled, }; class PeerListDelegate { @@ -179,9 +178,11 @@ public: virtual void peerListAppendSearchRow(std::unique_ptr row) = 0; virtual void peerListAppendFoundRow(gsl::not_null row) = 0; virtual void peerListPrependRow(std::unique_ptr row) = 0; + virtual void peerListPrependRowFromSearchResult(gsl::not_null row) = 0; virtual void peerListUpdateRow(gsl::not_null row) = 0; - virtual bool peerListIsRowSelected(gsl::not_null peer) = 0; virtual void peerListRemoveRow(gsl::not_null row) = 0; + virtual void peerListConvertRowToSearchResult(gsl::not_null row) = 0; + virtual bool peerListIsRowSelected(gsl::not_null peer) = 0; virtual void peerListSetRowChecked(gsl::not_null row, bool checked) = 0; virtual gsl::not_null peerListRowAt(int index) = 0; virtual void peerListRefreshRows() = 0; @@ -267,6 +268,7 @@ public: virtual bool searchInLocal() { return true; } + bool hasComplexSearch() const; void search(const QString &query); void peerListSearchAddRow(gsl::not_null peer) override; @@ -316,8 +318,10 @@ public: void peerListAppendSearchRow(std::unique_ptr row) override; void peerListAppendFoundRow(gsl::not_null row) override; void peerListPrependRow(std::unique_ptr row) override; + void peerListPrependRowFromSearchResult(gsl::not_null row) override; void peerListUpdateRow(gsl::not_null row) override; void peerListRemoveRow(gsl::not_null row) override; + void peerListConvertRowToSearchResult(gsl::not_null row) override; void peerListSetRowChecked(gsl::not_null row, bool checked) override; gsl::not_null peerListRowAt(int index) override; bool peerListIsRowSelected(gsl::not_null peer) override; @@ -381,11 +385,13 @@ public: void appendSearchRow(std::unique_ptr row); void appendFoundRow(gsl::not_null row); void prependRow(std::unique_ptr row); + void prependRowFromSearchResult(gsl::not_null row); PeerListRow *findRow(PeerListRowId id); void updateRow(gsl::not_null row) { updateRow(row, RowIndex()); } void removeRow(gsl::not_null row); + void convertRowToSearchResult(gsl::not_null row); int fullRowsCount() const; gsl::not_null rowAt(int index) const; void setDescription(object_ptr description); @@ -422,6 +428,7 @@ protected: private: void refreshIndices(); + void removeRowAtIndex(std::vector> &from, int index); void invalidatePixmapsCache(); @@ -494,7 +501,7 @@ private: void clearSearchRows(); gsl::not_null _controller; - PeerListSearchMode _searchMode = PeerListSearchMode::None; + PeerListSearchMode _searchMode = PeerListSearchMode::Disabled; int _rowHeight = 0; int _visibleTop = 0; diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp index 873c7d816..3e9c62a51 100644 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp +++ b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp @@ -37,7 +37,7 @@ constexpr auto kParticipantsPerPage = 200; } // namespace -ParticipantsBoxController::ParticipantsBoxController(gsl::not_null channel, Role role) : PeerListController((role == Role::Admins) ? nullptr : std::make_unique(channel, role, &_additional)) +ParticipantsBoxController::ParticipantsBoxController(gsl::not_null channel, Role role) : PeerListController(CreateSearchController(channel, role, &_additional)) , _channel(channel) , _role(role) { if (_channel->mgInfo) { @@ -45,6 +45,14 @@ ParticipantsBoxController::ParticipantsBoxController(gsl::not_null } } +std::unique_ptr ParticipantsBoxController::CreateSearchController(gsl::not_null channel, Role role, gsl::not_null additional) { + // In admins box complex search is used for adding new admins. + if (role != Role::Admins || channel->canAddAdmins()) { + return std::make_unique(channel, role, additional); + } + return nullptr; +} + void ParticipantsBoxController::Start(gsl::not_null channel, Role role) { auto controller = std::make_unique(channel, role); auto initBox = [role, channel, controller = controller.get()](PeerListBox *box) { @@ -103,7 +111,6 @@ void ParticipantsBoxController::addNewItem() { } void ParticipantsBoxController::peerListSearchAddRow(gsl::not_null peer) { - Expects(_role != Role::Admins); PeerListController::peerListSearchAddRow(peer); if (_role == Role::Restricted && delegate()->peerListFullRowsCount() > 0) { setDescriptionText(QString()); @@ -168,19 +175,17 @@ void ParticipantsBoxController::HandleParticipant(const MTPChannelParticipant &p } void ParticipantsBoxController::prepare() { - if (_role == Role::Admins) { - delegate()->peerListSetSearchMode(PeerListSearchMode::Local); - delegate()->peerListSetTitle(langFactory(lng_channel_admins)); - } else { - delegate()->peerListSetSearchMode(PeerListSearchMode::Complex); - if (_role == Role::Members) { - delegate()->peerListSetTitle(langFactory(lng_profile_participants_section)); - } else if (_role == Role::Restricted) { - delegate()->peerListSetTitle(langFactory(lng_restricted_list_title)); - } else { - delegate()->peerListSetTitle(langFactory(lng_banned_list_title)); + auto titleKey = [this] { + switch (_role) { + case Role::Admins: return lng_channel_admins; + case Role::Members: return lng_profile_participants_section; + case Role::Restricted: return lng_restricted_list_title; + case Role::Kicked: return lng_banned_list_title; } - } + Unexpected("Role in ParticipantsBoxController::prepare()"); + }; + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + delegate()->peerListSetTitle(langFactory(titleKey())); setDescriptionText(lang(lng_contacts_loading)); setSearchNoResultsText(lang(lng_blocked_list_not_found)); @@ -317,17 +322,16 @@ void ParticipantsBoxController::rowActionClicked(gsl::not_null row void ParticipantsBoxController::showAdmin(gsl::not_null user) { auto it = _additional.adminRights.find(user); - if (it == _additional.adminRights.cend()) { - if (user != _additional.creator) { - return; - } - } - auto currentRights = (user == _additional.creator) + auto isCreator = (user == _additional.creator); + auto notAdmin = !isCreator && (it == _additional.adminRights.cend()); + auto currentRights = isCreator ? MTP_channelAdminRights(MTP_flags(~MTPDchannelAdminRights::Flag::f_add_admins | MTPDchannelAdminRights::Flag::f_add_admins)) - : it->second; + : notAdmin ? MTP_channelAdminRights(MTP_flags(0)) : it->second; auto weak = base::weak_unique_ptr(this); auto box = Box(_channel, user, currentRights); - if (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()) { + auto canEdit = (_additional.adminCanEdit.find(user) != _additional.adminCanEdit.end()); + auto canSave = notAdmin ? _channel->canAddAdmins() : canEdit; + if (canSave) { box->setSaveCallback([channel = _channel.get(), user, weak](const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights) { MTP::send(MTPchannels_EditAdmin(channel->inputChannel, user->inputUser, newRights), rpcDone([channel, user, weak, oldRights, newRights](const MTPUpdates &result) { AuthSession::Current().api().applyUpdates(result); @@ -477,7 +481,12 @@ bool ParticipantsBoxController::appendRow(gsl::not_null user) { } bool ParticipantsBoxController::prependRow(gsl::not_null user) { - if (delegate()->peerListFindRow(user->id)) { + if (auto row = delegate()->peerListFindRow(user->id)) { + if (_role == Role::Admins) { + // Perhaps we've added a new admin from search. + refreshAdminCustomStatus(row); + delegate()->peerListPrependRowFromSearchResult(row); + } return false; } delegate()->peerListPrependRow(createRow(user)); @@ -489,7 +498,13 @@ bool ParticipantsBoxController::prependRow(gsl::not_null user) { bool ParticipantsBoxController::removeRow(gsl::not_null user) { if (auto row = delegate()->peerListFindRow(user->id)) { - delegate()->peerListRemoveRow(row); + if (_role == Role::Admins) { + // Perhaps we are removing an admin from search results. + row->setCustomStatus(lang(lng_channel_admin_status_not_admin)); + delegate()->peerListConvertRowToSearchResult(row); + } else { + delegate()->peerListRemoveRow(row); + } if (!delegate()->peerListFullRowsCount()) { setDescriptionText(lang(lng_blocked_list_not_found)); } @@ -501,12 +516,7 @@ bool ParticipantsBoxController::removeRow(gsl::not_null user) { std::unique_ptr ParticipantsBoxController::createRow(gsl::not_null user) const { auto row = std::make_unique(user); if (_role == Role::Admins) { - auto promotedBy = _additional.adminPromotedBy.find(user); - if (promotedBy == _additional.adminPromotedBy.cend()) { - row->setCustomStatus(lang(lng_channel_admin_status_creator)); - } else { - row->setCustomStatus(lng_channel_admin_status_promoted_by(lt_user, App::peerName(promotedBy->second))); - } + refreshAdminCustomStatus(row.get()); } if (_role == Role::Restricted || (_role == Role::Admins && _additional.adminCanEdit.find(user) != _additional.adminCanEdit.cend())) { // row->setActionLink(lang(lng_profile_edit_permissions)); @@ -522,11 +532,24 @@ std::unique_ptr ParticipantsBoxController::createRow(gsl::not_null< return std::move(row); } +void ParticipantsBoxController::refreshAdminCustomStatus(gsl::not_null row) const { + auto user = row->peer()->asUser(); + auto promotedBy = _additional.adminPromotedBy.find(user); + if (promotedBy == _additional.adminPromotedBy.cend()) { + if (user == _additional.creator) { + row->setCustomStatus(lang(lng_channel_admin_status_creator)); + } else { + row->setCustomStatus(lang(lng_channel_admin_status_not_admin)); + } + } else { + row->setCustomStatus(lng_channel_admin_status_promoted_by(lt_user, App::peerName(promotedBy->second))); + } +} + ParticipantsBoxSearchController::ParticipantsBoxSearchController(gsl::not_null channel, Role role, gsl::not_null additional) : _channel(channel) , _role(role) , _additional(additional) { - Expects(role != Role::Admins); _timer.setCallback([this] { searchOnServer(); }); } @@ -570,6 +593,7 @@ bool ParticipantsBoxSearchController::loadMoreRows() { if (!_allLoaded && !isLoading()) { auto filter = [this] { switch (_role) { + case Role::Admins: // Search for members, appoint as admin on found. case Role::Members: return MTP_channelParticipantsSearch(MTP_string(_query)); case Role::Restricted: return MTP_channelParticipantsBanned(MTP_string(_query)); case Role::Kicked: return MTP_channelParticipantsKicked(MTP_string(_query)); @@ -627,8 +651,9 @@ void ParticipantsBoxSearchController::searchDone(mtpRequestId requestId, const M // wait for an empty results list unlike the non-search peer list. _allLoaded = true; } + auto parseRole = (_role == Role::Admins) ? Role::Members : _role; for_const (auto &participant, list) { - ParticipantsBoxController::HandleParticipant(participant, _role, _additional, [this](gsl::not_null user) { + ParticipantsBoxController::HandleParticipant(participant, parseRole, _additional, [this](gsl::not_null user) { delegate()->peerListSearchAddRow(user); }); } @@ -657,7 +682,7 @@ std::unique_ptr AddParticipantBoxController::createSearchRow(gsl::n } void AddParticipantBoxController::prepare() { - delegate()->peerListSetSearchMode(PeerListSearchMode::Complex); + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); auto title = [this] { switch (_role) { case Role::Admins: return langFactory(lng_channel_add_admin); diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.h b/Telegram/SourceFiles/profile/profile_channel_controllers.h index 262d66cb5..4e3fa57a6 100644 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.h +++ b/Telegram/SourceFiles/profile/profile_channel_controllers.h @@ -65,6 +65,8 @@ public: static void HandleParticipant(const MTPChannelParticipant &participant, Role role, gsl::not_null additional, Callback callback); private: + static std::unique_ptr CreateSearchController(gsl::not_null channel, Role role, gsl::not_null additional); + void showAdmin(gsl::not_null user); void editAdminDone(gsl::not_null user, const MTPChannelAdminRights &rights); void showRestricted(gsl::not_null user); @@ -76,6 +78,7 @@ private: bool prependRow(gsl::not_null user); bool removeRow(gsl::not_null user); std::unique_ptr createRow(gsl::not_null user) const; + void refreshAdminCustomStatus(gsl::not_null row) const; bool feedMegagroupLastParticipants(); gsl::not_null _channel;