diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e41e0b12eb..dedca84f8c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5981,9 +5981,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_edit_admin_header" = "What can this admin do?"; "lng_rights_edit_admin_rank_name" = "Custom title"; "lng_rights_edit_admin_rank_about" = "A title that members will see instead of '{title}'."; -"lng_manage_peer_custom_ranks_title" = "Custom Member Titles"; -"lng_manage_peer_custom_ranks" = "Allow Custom Titles"; -"lng_manage_peer_custom_ranks_about" = "Allow members to set custom titles that will be shown next to their names in messages."; +"lng_manage_peer_custom_tags" = "Allow Members Tags"; +"lng_manage_peer_custom_tags_about" = "Turn this on to let members of the group add short tags next to their names."; +"lng_context_add_my_tag" = "Add Tag"; +"lng_context_edit_my_tag" = "Edit Tag"; +"lng_context_add_member_tag" = "Add Member Tag"; +"lng_context_edit_member_tag" = "Edit Member Tag"; "lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with equal or fewer rights."; "lng_rights_about_add_admins_no" = "This admin will not be able to add new admins."; "lng_rights_about_by" = "This admin promoted by {user} on {date}."; diff --git a/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp b/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp index 0fd9fd007b..a2b5af312d 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp @@ -34,44 +34,80 @@ namespace { [[nodiscard]] object_ptr CreateMembersVisibleButton( not_null megagroup) { - auto result = object_ptr((QWidget*)nullptr); - const auto container = result.data(); - const auto min = EnableHideMembersMin(megagroup); - if (!megagroup->canBanMembers() || megagroup->membersCount() < min) { + const auto showHideMembers = megagroup->canBanMembers() + && megagroup->membersCount() >= min; + const auto showCustomTags = megagroup->amCreator(); + if (!showHideMembers && !showCustomTags) { return { nullptr }; } - struct State { - rpl::event_stream toggled; - }; - Ui::AddSkip(container); - const auto state = container->lifetime().make_state(); - const auto button = container->add( - EditPeerInfoBox::CreateButton( - container, - tr::lng_profile_hide_participants(), - rpl::single(QString()), - [] {}, - st::manageGroupNoIconButton, - {} - ))->toggleOn(rpl::single( - (megagroup->flags() & ChannelDataFlag::ParticipantsHidden) != 0 - ) | rpl::then(state->toggled.events())); - Ui::AddSkip(container); - Ui::AddDividerText(container, tr::lng_profile_hide_participants_about()); + auto result = object_ptr((QWidget*)nullptr); + const auto container = result.data(); - button->toggledValue( - ) | rpl::on_next([=](bool toggled) { - megagroup->session().api().request( - MTPchannels_ToggleParticipantsHidden( - megagroup->inputChannel(), - MTP_bool(toggled) - ) - ).done([=](const MTPUpdates &result) { - megagroup->session().api().applyUpdates(result); - }).send(); - }, button->lifetime()); + if (showHideMembers) { + struct State { + rpl::event_stream toggled; + }; + Ui::AddSkip(container); + const auto state = container->lifetime().make_state(); + const auto button = container->add( + EditPeerInfoBox::CreateButton( + container, + tr::lng_profile_hide_participants(), + rpl::single(QString()), + [] {}, + st::manageGroupNoIconButton, + {} + ))->toggleOn(rpl::single( + (megagroup->flags() & ChannelDataFlag::ParticipantsHidden) != 0 + ) | rpl::then(state->toggled.events())); + Ui::AddSkip(container); + Ui::AddDividerText( + container, + tr::lng_profile_hide_participants_about()); + + button->toggledValue( + ) | rpl::on_next([=](bool toggled) { + megagroup->session().api().request( + MTPchannels_ToggleParticipantsHidden( + megagroup->inputChannel(), + MTP_bool(toggled) + ) + ).done([=](const MTPUpdates &result) { + megagroup->session().api().applyUpdates(result); + }).send(); + }, button->lifetime()); + } + + if (showCustomTags) { + Ui::AddSkip(container); + const auto tagsButton = container->add( + EditPeerInfoBox::CreateButton( + container, + tr::lng_manage_peer_custom_tags(), + rpl::single(QString()), + [] {}, + st::manageGroupNoIconButton, + {} + ))->toggleOn(rpl::single(megagroup->customRanksEnabled())); + Ui::AddSkip(container); + Ui::AddDividerText( + container, + tr::lng_manage_peer_custom_tags_about()); + + tagsButton->toggledValue( + ) | rpl::on_next([=](bool toggled) { + megagroup->session().api().request( + MTPmessages_ToggleChatCustomRanks( + megagroup->input(), + MTP_bool(toggled) + ) + ).done([=](const MTPUpdates &result) { + megagroup->session().api().applyUpdates(result); + }).send(); + }, tagsButton->lifetime()); + } return result; } diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index 94f6952493..28ad8156ea 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -30,12 +30,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "base/unixtime.h" #include "ui/effects/outline_segments.h" +#include "ui/layers/generic_box.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/menu/menu_multiline_action.h" #include "ui/widgets/popup_menu.h" #include "ui/text/text_utilities.h" #include "info/profile/info_profile_values.h" #include "window/window_session_controller.h" #include "history/history.h" +#include "styles/style_boxes.h" #include "styles/style_chat.h" #include "styles/style_menu_icons.h" @@ -168,6 +171,48 @@ void SaveChannelAdmin( }).send(); } +void SaveMemberRank( + std::shared_ptr show, + not_null peer, + not_null user, + const QString &rank, + Fn onDone, + Fn onFail) { + peer->session().api().request(MTPmessages_EditChatParticipantRank( + peer->input(), + user->input(), + MTP_string(rank) + )).done([=](const MTPUpdates &result) { + peer->session().api().applyUpdates(result); + if (const auto channel = peer->asChannel()) { + channel->applyEditMemberRank(user, rank); + } else if (const auto chat = peer->asChat()) { + if (rank.isEmpty()) { + chat->memberRanks.remove(peerToUser(user->id)); + } else { + chat->memberRanks[peerToUser(user->id)] = rank; + } + chat->session().changes().peerUpdated( + chat, + Data::PeerUpdate::Flag::Members); + } + peer->session().changes().chatMemberRankChanged( + peer, + user, + rank); + if (onDone) { + onDone(); + } + }).fail([=](const MTP::Error &error) { + if (show) { + show->showToast(error.type()); + } + if (onFail) { + onFail(); + } + }).send(); +} + void SaveChatParticipantKick( not_null chat, not_null user, @@ -616,6 +661,16 @@ void ParticipantsAdditionalData::applyBannedLocally( } } +void ParticipantsAdditionalData::applyMemberRankLocally( + not_null user, + const QString &rank) { + if (rank.isEmpty()) { + _memberRanks.remove(user); + } else { + _memberRanks[user] = rank; + } +} + PeerData *ParticipantsAdditionalData::applyParticipant( const Api::ChatParticipant &data) { return applyParticipant(data, _role); @@ -1316,6 +1371,16 @@ void ParticipantsBoxController::prepare() { recomputeTypeFor(user); refreshRows(); }, lifetime()); + + _peer->session().changes().chatMemberRankChanges( + ) | rpl::on_next([=](const Data::ChatMemberRankChange &update) { + if (update.peer != _peer) { + return; + } + _additional.applyMemberRankLocally(update.user, update.rank); + recomputeTypeFor(update.user); + refreshRows(); + }, lifetime()); } void ParticipantsBoxController::unload() { @@ -1754,6 +1819,57 @@ base::unique_qptr ParticipantsBoxController::rowContextMenu( ? &st::menuIconProfile : &st::menuIconInfo)); } + if (user) { + const auto isAdmin = _peer->isChat() + ? (_peer->asChat()->hasAdminRights() + || _peer->asChat()->amCreator()) + : (_peer->isChannel() + ? (_peer->asChannel()->hasAdminRights() + || _peer->asChannel()->amCreator()) + : false); + const auto isSelf = user->isSelf(); + const auto canEditSelf = isSelf + && (isAdmin + || (_peer->isChat() + ? _peer->asChat()->customRanksEnabled() + : (_peer->isChannel() + ? _peer->asChannel()->customRanksEnabled() + : false))); + const auto targetIsAdmin = _additional.adminRights(user).has_value() + || _additional.isCreator(user); + const auto canEditTarget = !isSelf + && isAdmin + && (!targetIsAdmin || _additional.canEditAdmin(user)); + if (canEditSelf || canEditTarget) { + const auto currentRank = _additional.memberRank(user); + const auto show = delegate()->peerListUiShow(); + const auto peer = _peer; + const auto actionText = canEditSelf + ? (currentRank.isEmpty() + ? tr::lng_context_add_my_tag(tr::now) + : tr::lng_context_edit_my_tag(tr::now)) + : (currentRank.isEmpty() + ? tr::lng_context_add_member_tag(tr::now) + : tr::lng_context_edit_member_tag(tr::now)); + result->addAction( + actionText, + crl::guard(this, [=] { + _editBox = show->show(Box( + EditCustomRankBox, + show, + peer, + user, + currentRank, + canEditSelf, + crl::guard(this, [=](const QString &rank) { + _additional.applyMemberRankLocally(user, rank); + recomputeTypeFor(user); + refreshRows(); + }))); + }), + &st::menuIconEdit); + } + } if (_role == Role::Kicked) { if (_peer->isMegagroup() && _additional.canRestrictParticipant(participant)) { @@ -2464,3 +2580,71 @@ void ParticipantsBoxSearchController::searchDone( delegate()->peerListSearchRefreshRows(); } + +void EditCustomRankBox( + not_null box, + std::shared_ptr show, + not_null peer, + not_null user, + const QString ¤tRank, + bool isSelf, + Fn onSaved) { + constexpr auto kRankLimit = 16; + struct State { + bool saving = false; + }; + const auto state = box->lifetime().make_state(); + + const auto hasRank = !currentRank.isEmpty(); + box->setTitle(isSelf + ? (hasRank + ? tr::lng_context_edit_my_tag() + : tr::lng_context_add_my_tag()) + : (hasRank + ? tr::lng_context_edit_member_tag() + : tr::lng_context_add_member_tag())); + + const auto field = box->addRow(object_ptr( + box, + st::customBadgeField, + tr::lng_rights_edit_admin_rank_name(), + TextUtilities::RemoveEmoji(currentRank))); + field->setMaxLength(kRankLimit); + field->setInstantReplaces(Ui::InstantReplaces::TextOnly()); + field->changes( + ) | rpl::on_next([=] { + const auto text = field->getLastText(); + const auto removed = TextUtilities::RemoveEmoji(text); + if (removed != text) { + field->setText(removed); + } + }, field->lifetime()); + + box->setFocusCallback([=] { field->setFocusFast(); }); + + const auto close = crl::guard(box, [=] { box->closeBox(); }); + const auto save = [=] { + if (state->saving) { + return; + } + state->saving = true; + const auto rank = TextUtilities::RemoveEmoji( + TextUtilities::SingleLine(field->getLastText().trimmed())); + SaveMemberRank( + show, + peer, + user, + rank, + [=] { + if (onSaved) { + onSaved(rank); + } + close(); + }, + [=] { state->saving = false; }); + }; + field->submits( + ) | rpl::on_next([=] { save(); }, field->lifetime()); + box->addButton(tr::lng_settings_save(), save); + box->addButton(tr::lng_cancel(), close); +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.h b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h index 91cc64375d..8bfa406f84 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h @@ -49,6 +49,15 @@ Fn onDone, Fn onFail); +void EditCustomRankBox( + not_null box, + std::shared_ptr show, + not_null peer, + not_null user, + const QString ¤tRank, + bool isSelf, + Fn onSaved); + void SubscribeToMigration( not_null peer, rpl::lifetime &lifetime, @@ -130,6 +139,9 @@ public: void applyBannedLocally( not_null participant, ChatRestrictionsInfo rights); + void applyMemberRankLocally( + not_null user, + const QString &rank); private: UserData *applyCreator(const Api::ChatParticipant &data); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index f5613e4c1d..f3c2c6fc43 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -405,7 +405,6 @@ private: std::optional signatures; std::optional signatureProfiles; std::optional noForwards; - std::optional customRanks; std::optional joinToWrite; std::optional requestToJoin; std::optional discussionLink; @@ -466,7 +465,6 @@ private: [[nodiscard]] bool validateAutotranslate(Saving &to) const; [[nodiscard]] bool validateSignatures(Saving &to) const; [[nodiscard]] bool validateForwards(Saving &to) const; - [[nodiscard]] bool validateCustomRanks(Saving &to) const; [[nodiscard]] bool validateJoinToWrite(Saving &to) const; [[nodiscard]] bool validateRequestToJoin(Saving &to) const; @@ -482,7 +480,6 @@ private: void saveAutotranslate(); void saveSignatures(); void saveForwards(); - void saveCustomRanks(); void saveJoinToWrite(); void saveRequestToJoin(); void savePhoto(); @@ -1002,11 +999,6 @@ void Controller::fillPrivacyTypeButton() { ? _peer->asChannel()->usernames() : std::vector()), .noForwards = !_peer->allowsForwarding(), - .customRanks = (_peer->isChat() - ? _peer->asChat()->customRanksEnabled() - : _peer->isChannel() - ? _peer->asChannel()->customRanksEnabled() - : false), .joinToWrite = (_peer->isMegagroup() && _peer->asChannel()->joinToWrite()), .requestToJoin = (_peer->isMegagroup() @@ -2104,7 +2096,6 @@ std::optional Controller::validate() const { && validateAutotranslate(result) && validateSignatures(result) && validateForwards(result) - && validateCustomRanks(result) && validateJoinToWrite(result) && validateRequestToJoin(result)) { return result; @@ -2227,14 +2218,6 @@ bool Controller::validateForwards(Saving &to) const { return true; } -bool Controller::validateCustomRanks(Saving &to) const { - if (!_typeDataSavedValue) { - return true; - } - to.customRanks = _typeDataSavedValue->customRanks; - return true; -} - bool Controller::validateJoinToWrite(Saving &to) const { if (!_typeDataSavedValue) { return true; @@ -2270,7 +2253,6 @@ void Controller::save() { pushSaveStage([=] { saveAutotranslate(); }); pushSaveStage([=] { saveSignatures(); }); pushSaveStage([=] { saveForwards(); }); - pushSaveStage([=] { saveCustomRanks(); }); pushSaveStage([=] { saveJoinToWrite(); }); pushSaveStage([=] { saveRequestToJoin(); }); pushSaveStage([=] { savePhoto(); }); @@ -2779,32 +2761,6 @@ void Controller::saveForwards() { }).send(); } -void Controller::saveCustomRanks() { - const auto isEnabled = _peer->isChat() - ? _peer->asChat()->customRanksEnabled() - : _peer->isChannel() - ? _peer->asChannel()->customRanksEnabled() - : false; - if (!_savingData.customRanks - || *_savingData.customRanks == isEnabled) { - return continueSave(); - } - _api.request(MTPmessages_ToggleChatCustomRanks( - _peer->input(), - MTP_bool(*_savingData.customRanks) - )).done([=](const MTPUpdates &result) { - _peer->session().api().applyUpdates(result); - continueSave(); - }).fail([=](const MTP::Error &error) { - if (error.type() == u"CHAT_NOT_MODIFIED"_q) { - continueSave(); - } else { - _navigation->showToast(error.type()); - cancelSave(); - } - }).send(); -} - void Controller::saveJoinToWrite() { const auto joinToWrite = _peer->isMegagroup() && _peer->asChannel()->joinToWrite(); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp index 2218ee64ac..0373cc6a16 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp @@ -83,10 +83,6 @@ public: [[nodiscard]] bool noForwards() const { return _controls.noForwards->toggled(); } - [[nodiscard]] bool customRanks() const { - return _controls.customRanks - && _controls.customRanks->toggled(); - } [[nodiscard]] bool joinToWrite() const { return _controls.joinToWrite && _controls.joinToWrite->toggled(); } @@ -116,7 +112,6 @@ private: Ui::SlideWrap *whoSendWrap = nullptr; Ui::SettingsButton *noForwards = nullptr; - Ui::SettingsButton *customRanks = nullptr; Ui::SettingsButton *joinToWrite = nullptr; Ui::SettingsButton *requestToJoin = nullptr; }; @@ -302,34 +297,6 @@ void Controller::createContent() { ? tr::lng_manage_peer_no_forwards_about : tr::lng_manage_peer_no_forwards_about_channel)()); - const auto amCreator = (_peer->isChat() - && _peer->asChat()->amCreator()) - || (_peer->isChannel() - && _peer->asChannel()->amCreator()); - if (amCreator) { - Ui::AddSkip(_wrap.get()); - Ui::AddSubsectionTitle( - _wrap.get(), - tr::lng_manage_peer_custom_ranks_title()); - _controls.customRanks = _wrap->add( - EditPeerInfoBox::CreateButton( - _wrap.get(), - tr::lng_manage_peer_custom_ranks(), - rpl::single(QString()), - [] {}, - st::peerPermissionsButton, - {})); - _controls.customRanks->toggleOn( - rpl::single(_dataSavedValue->customRanks) - )->toggledValue( - ) | rpl::on_next([=](bool toggled) { - _dataSavedValue->customRanks = toggled; - }, _wrap->lifetime()); - Ui::AddSkip(_wrap.get()); - Ui::AddDividerText( - _wrap.get(), - tr::lng_manage_peer_custom_ranks_about()); - } } if (_linkOnly) { _controls.inviteLinkWrap->show(anim::type::instant); @@ -811,7 +778,6 @@ void EditPeerTypeBox::prepare() { ? controller->usernamesOrder() : std::vector()), .noForwards = controller->noForwards(), - .customRanks = controller->customRanks(), .joinToWrite = controller->joinToWrite(), .requestToJoin = controller->requestToJoin(), }); // We don't need username with private type. diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h index 3238894481..7b9aba6f9a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h @@ -39,7 +39,6 @@ struct EditPeerTypeData { std::vector usernamesOrder; bool hasDiscussionLink = false; bool noForwards = false; - bool customRanks = false; bool joinToWrite = false; bool requestToJoin = false; }; diff --git a/Telegram/SourceFiles/data/data_changes.cpp b/Telegram/SourceFiles/data/data_changes.cpp index 5356d48065..8b5d73c9a8 100644 --- a/Telegram/SourceFiles/data/data_changes.cpp +++ b/Telegram/SourceFiles/data/data_changes.cpp @@ -357,6 +357,17 @@ rpl::producer Changes::chatAdminChanges() const { return _chatAdminChanges.events(); } +void Changes::chatMemberRankChanged( + not_null peer, + not_null user, + QString rank) { + _chatMemberRankChanges.fire({ peer, user, std::move(rank) }); +} + +rpl::producer Changes::chatMemberRankChanges() const { + return _chatMemberRankChanges.events(); +} + void Changes::scheduleNotifications() { if (!_notify) { _notify = true; diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 2471bf2e2c..f5c66416af 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -282,6 +282,12 @@ struct ChatAdminChange { QString rank; }; +struct ChatMemberRankChange { + not_null peer; + not_null user; + QString rank; +}; + class Changes final { public: explicit Changes(not_null session); @@ -401,6 +407,12 @@ public: QString rank); [[nodiscard]] rpl::producer chatAdminChanges() const; + void chatMemberRankChanged( + not_null peer, + not_null user, + QString rank); + [[nodiscard]] rpl::producer chatMemberRankChanges() const; + void sendNotifications(); private: @@ -454,6 +466,7 @@ private: Manager _entryChanges; Manager _storyChanges; rpl::event_stream _chatAdminChanges; + rpl::event_stream _chatMemberRankChanges; bool _notify = false; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 94049d9270..f3df9e3ab2 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -510,6 +510,17 @@ void ChannelData::applyEditAdmin( session().changes().peerUpdated(this, UpdateFlag::Admins); } +void ChannelData::applyEditMemberRank( + not_null user, + const QString &rank) { + if (!mgInfo) { + return; + } + const auto userId = peerToUser(user->id); + Data::ChannelMemberRankChanges changes(this); + changes.feed(userId, rank); +} + void ChannelData::applyEditBanned( not_null participant, ChatRestrictionsInfo oldRights, diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index c805f29fa2..c49e904e16 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -308,6 +308,9 @@ public: ChatAdminRightsInfo oldRights, ChatAdminRightsInfo newRights, const QString &rank); + void applyEditMemberRank( + not_null user, + const QString &rank); void applyEditBanned( not_null participant, ChatRestrictionsInfo oldRights, diff --git a/Telegram/SourceFiles/data/data_channel_admins.cpp b/Telegram/SourceFiles/data/data_channel_admins.cpp index e7ac8ced18..6dd286573e 100644 --- a/Telegram/SourceFiles/data/data_channel_admins.cpp +++ b/Telegram/SourceFiles/data/data_channel_admins.cpp @@ -44,4 +44,37 @@ ChannelAdminChanges::~ChannelAdminChanges() { } } +ChannelMemberRankChanges::ChannelMemberRankChanges( + not_null channel) +: _channel(channel) +, _memberRanks(_channel->mgInfo->memberRanks) { +} + +void ChannelMemberRankChanges::feed( + UserId userId, + const QString &rank) { + if (rank.isEmpty()) { + if (_memberRanks.remove(userId)) { + _changes.emplace(userId); + } + } else { + const auto i = _memberRanks.find(userId); + if (i == end(_memberRanks) || i->second != rank) { + _memberRanks[userId] = rank; + _changes.emplace(userId); + } + } +} + +ChannelMemberRankChanges::~ChannelMemberRankChanges() { + if (_changes.size() > 1 + || (!_changes.empty() + && _changes.front() != _channel->session().userId())) { + if (const auto history + = _channel->owner().historyLoaded(_channel)) { + history->applyGroupAdminChanges(_changes); + } + } +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_channel_admins.h b/Telegram/SourceFiles/data/data_channel_admins.h index 7fa1b06fd9..b069e57a49 100644 --- a/Telegram/SourceFiles/data/data_channel_admins.h +++ b/Telegram/SourceFiles/data/data_channel_admins.h @@ -25,4 +25,19 @@ private: }; +class ChannelMemberRankChanges { +public: + ChannelMemberRankChanges(not_null channel); + + void feed(UserId userId, const QString &rank); + + ~ChannelMemberRankChanges(); + +private: + not_null _channel; + base::flat_map &_memberRanks; + base::flat_set _changes; + +}; + } // namespace Data diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 10f1946401..1a2b4368ac 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -8964,6 +8964,7 @@ void HistoryWidget::fillSenderUserpicMenu( Window::FillSenderUserpicMenu( controller(), peer, + inGroup ? _peer : nullptr, (inGroup && _canSendTexts) ? _field.data() : nullptr, inGroup ? _peer->owner().history(_peer) : Dialogs::Key(), Ui::Menu::CreateAddActionCallback(menu)); diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index fdbc38b1e1..10d809ff1c 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -3323,9 +3323,15 @@ base::unique_qptr ChatWidget::listFillSenderUserpicMenu( auto menu = base::make_unique_q( this, st::popupMenuWithIcons); + const auto senderPeer = _history->owner().peer(userpicPeerId); + const auto groupPeer = (_history->peer->isChat() + || _history->peer->isMegagroup()) + ? _history->peer.get() + : nullptr; Window::FillSenderUserpicMenu( controller(), - _history->owner().peer(userpicPeerId), + senderPeer, + groupPeer, _composeControls->fieldForMention(), searchInEntry, Ui::Menu::CreateAddActionCallback(menu.get())); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 83a197b615..600360a928 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -109,6 +109,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/ui_integration.h" #include "export/export_manager.h" +#include "boxes/peers/edit_participants_box.h" #include "boxes/peers/edit_peer_info_box.h" #include "boxes/premium_preview_box.h" #include "styles/style_chat.h" @@ -181,6 +182,24 @@ namespace { constexpr auto kArchivedToastDuration = crl::time(5000); constexpr auto kMaxUnreadWithoutConfirmation = 1000; +[[nodiscard]] QString LookupMemberRank( + not_null peer, + not_null user) { + if (const auto chat = peer->asChat()) { + const auto i = chat->memberRanks.find(peerToUser(user->id)); + return (i != chat->memberRanks.end()) ? i->second : QString(); + } else if (const auto channel = peer->asChannel()) { + if (channel->mgInfo) { + const auto i = channel->mgInfo->memberRanks.find( + peerToUser(user->id)); + return (i != channel->mgInfo->memberRanks.end()) + ? i->second + : QString(); + } + } + return QString(); +} + base::options::toggle ViewProfileInChatsListContextMenu({ .id = kOptionViewProfileInChatsListContextMenu, .name = "Add \"View Profile\"", @@ -3761,6 +3780,7 @@ bool FillVideoChatMenu( void FillSenderUserpicMenu( not_null controller, not_null peer, + PeerData *groupPeer, Ui::InputField *fieldForMention, Dialogs::Key searchInEntry, const PeerMenuCallback &addAction) { @@ -3803,6 +3823,57 @@ void FillSenderUserpicMenu( controller->searchInChat(searchInEntry, peer); }, &st::menuIconSearch); } + + if (const auto user = peer->asUser()) { + if (groupPeer) { + const auto isAdmin = groupPeer->isChat() + ? (groupPeer->asChat()->hasAdminRights() + || groupPeer->asChat()->amCreator()) + : (groupPeer->isChannel() + ? (groupPeer->asChannel()->hasAdminRights() + || groupPeer->asChannel()->amCreator()) + : false); + const auto canEditTarget = [&] { + if (const auto chat = groupPeer->asChat()) { + if (peerToUser(user->id) == chat->creator) { + return chat->amCreator(); + } + if (chat->admins.contains(user)) { + return chat->amCreator(); + } + return true; + } else if (const auto channel = groupPeer->asChannel()) { + if (channel->mgInfo + && (channel->mgInfo->lastAdmins.contains(user) + || channel->mgInfo->creator == user)) { + return channel->canEditAdmin(user); + } + return true; + } + return false; + }(); + if (isAdmin && canEditTarget && !user->isSelf()) { + const auto currentRank = LookupMemberRank( + groupPeer, + user); + addAction( + (currentRank.isEmpty() + ? tr::lng_context_add_member_tag(tr::now) + : tr::lng_context_edit_member_tag(tr::now)), + [=] { + controller->show(Box( + EditCustomRankBox, + controller->uiShow(), + groupPeer, + user, + currentRank, + false, + nullptr)); + }, + &st::menuIconEdit); + } + } + } } void AddSenderUserpicModerateAction( diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index 4a08e99f49..48910a4824 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -81,6 +81,7 @@ bool FillVideoChatMenu( void FillSenderUserpicMenu( not_null controller, not_null peer, + PeerData *groupPeer, Ui::InputField *fieldForMention, Dialogs::Key searchInEntry, const PeerMenuCallback &addAction);