Implement nice member/admin edit in groups.

This commit is contained in:
John Preston
2026-02-19 21:17:39 +04:00
parent 4d767fb279
commit 0f6a5480df
11 changed files with 245 additions and 40 deletions
+6
View File
@@ -6016,6 +6016,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_edit_tag_title" = "Edit tag";
"lng_rights_tag_about" = "Add short tag next to {name}'s name.";
"lng_rights_tag_about_self" = "Share your role, title, or how you're known in this group. Your tag is visible to all members.";
"lng_rights_promote_member" = "Promote to Admin";
"lng_rights_remove_member" = "Remove from Group";
"lng_rights_dismiss_admin" = "Dismiss Admin";
"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}.";
@@ -670,7 +670,7 @@ void ChatParticipants::Restrict(
ChatRestrictionsInfo oldRights,
ChatRestrictionsInfo newRights,
Fn<void()> onDone,
Fn<void()> onFail) {
Fn<void(const QString&)> onFail) {
channel->session().api().request(MTPchannels_EditBanned(
channel->inputChannel(),
participant->input(),
@@ -681,9 +681,9 @@ void ChatParticipants::Restrict(
if (onDone) {
onDone();
}
}).fail([=] {
}).fail([=](const MTP::Error &error) {
if (onFail) {
onFail();
onFail(error.type());
}
}).send();
}
@@ -113,7 +113,7 @@ public:
ChatRestrictionsInfo oldRights,
ChatRestrictionsInfo newRights,
Fn<void()> onDone,
Fn<void()> onFail);
Fn<void(const QString&)> onFail);
void add(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
@@ -1595,8 +1595,9 @@ void AddSpecialBoxController::showRestricted(
_editParticipantBox->closeBox();
}
});
const auto show = delegate()->peerListUiShow();
box->setSaveCallback(
SaveRestrictedCallback(_peer, user, done, fail));
SaveRestrictedCallback(show, _peer, user, done, fail));
}
_editParticipantBox = showBox(std::move(box));
}
@@ -1669,7 +1670,9 @@ void AddSpecialBoxController::kickUser(
const auto fail = crl::guard(this, [=] {
_editBox = nullptr;
});
const auto show = delegate()->peerListUiShow();
const auto callback = SaveRestrictedCallback(
show,
_peer,
participant,
done,
@@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/add_bot_to_chat_box.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "settings/settings_common.h"
#include "data/data_peer_values.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_settings.h"
namespace {
@@ -320,7 +322,8 @@ ChatAdminRightsInfo EditAdminBox::defaultRights() const {
| Flag::InviteByLinkOrAdd
| Flag::ManageTopics
| Flag::PinMessages
| Flag::ManageCall) }
| Flag::ManageCall
| Flag::ManageRanks) }
: ChatAdminRightsInfo{ (Flag::ChangeInfo
| Flag::PostMessages
| Flag::EditMessages
@@ -486,7 +489,6 @@ void EditAdminBox::prepare() {
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
if (hasRank) {
Ui::AddDivider(emptyAboutAddAdminsInner->entity());
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
}
Ui::AddSkip(aboutAddAdminsInner->entity());
Ui::AddDividerText(
@@ -525,7 +527,9 @@ void EditAdminBox::prepare() {
if (canSave()) {
if (hasRank) {
const auto role = LookupBadgeRole(peer(), user());
const auto role = _oldRights.flags
? LookupBadgeRole(peer(), user())
: HistoryView::BadgeRole::Admin;
_tagControl = inner->add(
object_ptr<EditTagControl>(
inner,
@@ -535,6 +539,52 @@ void EditAdminBox::prepare() {
role),
style::margins());
}
if (_tagControl) {
Ui::AddSkip(inner);
Ui::AddDividerText(
inner,
user()->isSelf()
? tr::lng_rights_tag_about_self()
: tr::lng_rights_tag_about(
lt_name,
rpl::single(user()->shortName())));
}
if (_oldRights.flags && !_addingBot) {
const auto isTargetCreator = (LookupBadgeRole(peer(), user())
== HistoryView::BadgeRole::Creator);
if (!isTargetCreator) {
if (!_tagControl) {
Ui::AddSkip(inner);
inner->add(
object_ptr<Ui::BoxContentDivider>(inner),
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
}
Ui::AddSkip(inner);
const auto dismissButton = Settings::AddButtonWithIcon(
inner,
tr::lng_rights_dismiss_admin(),
st::settingsAttentionButton,
{ nullptr });
dismissButton->setClickedCallback([=] {
if (const auto callback = _saveCallback) {
const auto old = _oldRights;
const auto confirmed = [=](Fn<void()> close) {
callback(old, ChatAdminRightsInfo(), QString());
close();
};
uiShow()->showBox(
Ui::MakeConfirmBox({
.text = tr::lng_profile_sure_remove_admin(
tr::now,
lt_user,
user()->firstName),
.confirmed = confirmed,
.confirmText = tr::lng_box_remove(),
}));
}
});
}
}
_finishSave = [=, value = getChecked] {
const auto newFlags = (value() | ChatAdminRight::Other)
& ((!channel || channel->amCreator())
@@ -722,6 +772,8 @@ void EditRestrictedBox::prepare() {
addControl(std::move(checkboxes), QMargins());
if (canSave() && peer()->canManageRanks()) {
Ui::AddSkip(verticalLayout());
Ui::AddDivider(verticalLayout());
_tagControl = addControl(
object_ptr<EditTagControl>(
this,
@@ -730,12 +782,22 @@ void EditRestrictedBox::prepare() {
_oldRank,
HistoryView::BadgeRole::User),
style::margins());
Ui::AddSkip(verticalLayout());
Ui::AddDividerText(
verticalLayout(),
user()->isSelf()
? tr::lng_rights_tag_about_self()
: tr::lng_rights_tag_about(
lt_name,
rpl::single(user()->shortName())));
}
_until = prepareRights.until;
addControl(
object_ptr<Ui::FixedHeightWidget>(this, st::defaultVerticalListSkip));
Ui::AddDivider(verticalLayout());
if (!_tagControl) {
addControl(
object_ptr<Ui::FixedHeightWidget>(this, st::defaultVerticalListSkip));
Ui::AddDivider(verticalLayout());
}
addControl(
object_ptr<Ui::FlatLabel>(
this,
@@ -774,6 +836,85 @@ void EditRestrictedBox::prepare() {
}
if (canSave()) {
Ui::AddSkip(verticalLayout());
Ui::AddDivider(verticalLayout());
Ui::AddSkip(verticalLayout());
const auto canAddAdmins = (chat && chat->canAddAdmins())
|| (channel && channel->canAddAdmins());
if (canAddAdmins) {
const auto promoteButton = Settings::AddButtonWithIcon(
verticalLayout(),
tr::lng_rights_promote_member(),
st::settingsButtonNoIcon,
{ nullptr });
promoteButton->setClickedCallback([=] {
const auto rank = _tagControl
? _tagControl->currentRank()
: _oldRank;
auto adminBox = Box<EditAdminBox>(
peer(),
user(),
ChatAdminRightsInfo(),
rank,
TimeId(0),
nullptr);
const auto adminBoxWeak = QPointer<Ui::BoxContent>(
adminBox.data());
const auto show = uiShow();
const auto restrictWeak = QPointer<EditRestrictedBox>(
this);
const auto closeBoth = [=] {
if (adminBoxWeak) {
adminBoxWeak->closeBox();
}
if (restrictWeak) {
restrictWeak->closeBox();
}
};
const auto done = [=](
ChatAdminRightsInfo,
const QString &) {
closeBoth();
};
const auto fail = closeBoth;
adminBox->setSaveCallback(
SaveAdminCallback(
show,
peer(),
user(),
done,
fail));
show->showBox(std::move(adminBox));
});
}
const auto removeButton = Settings::AddButtonWithIcon(
verticalLayout(),
tr::lng_rights_remove_member(),
st::settingsAttentionButton,
{ nullptr });
removeButton->setClickedCallback([=] {
if (const auto callback = _saveCallback) {
const auto old = _oldRights;
const auto confirmed = [=](Fn<void()> close) {
callback(
old,
ChannelData::KickedRestrictedRights(user()));
close();
};
uiShow()->show(
Ui::MakeConfirmBox({
.text = tr::lng_profile_sure_kick(
tr::now,
lt_user,
user()->firstName),
.confirmed = confirmed,
.confirmText = tr::lng_box_remove(),
}));
}
});
const auto save = [=, value = getRestrictions] {
if (!_saveCallback) {
return;
@@ -97,12 +97,12 @@ private:
[[nodiscard]] ChatAdminRightsInfo defaultRights() const;
void transferOwnership();
bool canSave() const {
[[nodiscard]] bool canSave() const {
return _saveCallback != nullptr;
}
void finishAddAdmin();
void refreshButtons();
bool canTransferOwnership() const;
[[nodiscard]] bool canTransferOwnership() const;
not_null<Ui::SlideWrap<Ui::RpWidget>*> setupTransferButton(
not_null<Ui::VerticalLayout*> container,
bool isGroup);
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/chat_style.h"
#include "ui/effects/outline_segments.h"
#include "ui/layers/generic_box.h"
#include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h"
@@ -56,6 +57,7 @@ constexpr auto kParticipantsPerPage = 200;
constexpr auto kSortByOnlineDelay = crl::time(1000);
void RemoveAdmin(
std::shared_ptr<Ui::Show> show,
not_null<ChannelData*> channel,
not_null<UserData*> user,
ChatAdminRightsInfo oldRights,
@@ -73,7 +75,10 @@ void RemoveAdmin(
if (onDone) {
onDone();
}
}).fail([=] {
}).fail([=](const MTP::Error &error) {
if (show) {
show->showToast(error.type());
}
if (onFail) {
onFail();
}
@@ -140,8 +145,13 @@ void SaveChatAdmin(
onFail,
false);
}, onFail);
} else if (onFail) {
onFail();
} else {
if (show) {
show->showToast(error.type());
}
if (onFail) {
onFail();
}
}
}).send();
}
@@ -175,6 +185,7 @@ void SaveChannelAdmin(
}
void SaveChatParticipantKick(
std::shared_ptr<Ui::Show> show,
not_null<ChatData*> chat,
not_null<UserData*> user,
Fn<void()> onDone,
@@ -188,7 +199,10 @@ void SaveChatParticipantKick(
if (onDone) {
onDone();
}
}).fail([=] {
}).fail([=](const MTP::Error &error) {
if (show) {
show->showToast(error.type());
}
if (onFail) {
onFail();
}
@@ -297,6 +311,7 @@ Fn<void(
Fn<void(
ChatRestrictionsInfo oldRights,
ChatRestrictionsInfo newRights)> SaveRestrictedCallback(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
not_null<PeerData*> participant,
Fn<void(ChatRestrictionsInfo newRights)> onDone,
@@ -312,12 +327,20 @@ Fn<void(
oldRights,
newRights,
done,
onFail);
[=](const QString &errorType) {
if (show) {
show->showToast(errorType);
}
if (onFail) {
onFail();
}
});
};
if (const auto chat = peer->asChatNotMigrated()) {
if (participant->isUser()
&& (newRights.flags & ChatRestriction::ViewMessages)) {
SaveChatParticipantKick(
show,
chat,
participant->asUser(),
done,
@@ -1721,6 +1744,16 @@ void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
&& (_peer->isChat() || _peer->isMegagroup())
&& user) {
showRestricted(user);
} else if (_role == Role::Members
&& user
&& (_additional.adminRights(user).has_value()
|| _additional.isCreator(user))
&& _additional.canAddOrEditAdmin(user)) {
showAdmin(user);
} else if (_role == Role::Members
&& user
&& _additional.canRestrictParticipant(participant)) {
showRestricted(user);
} else {
Assert(_navigation != nullptr);
if (_role != Role::Profile) {
@@ -2008,8 +2041,9 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
_editParticipantBox->closeBox();
}
});
const auto show = delegate()->peerListUiShow();
box->setSaveCallback(
SaveRestrictedCallback(_peer, user, done, fail));
SaveRestrictedCallback(show, _peer, user, done, fail));
}
_editParticipantBox = showBox(std::move(box));
}
@@ -2134,7 +2168,8 @@ void ParticipantsBoxController::removeAdminSure(not_null<UserData*> user) {
if (!adminRights) {
return;
}
RemoveAdmin(channel, user, *adminRights, crl::guard(this, [=] {
const auto show = delegate()->peerListUiShow();
RemoveAdmin(show, channel, user, *adminRights, crl::guard(this, [=] {
editAdminDone(
user,
ChatAdminRightsInfo(),
@@ -2670,14 +2705,7 @@ void EditCustomRankBox(
};
const auto state = box->lifetime().make_state<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()));
box->setTitle(tr::lng_rights_edit_tag_title());
const auto role = LookupBadgeRole(peer, user);
const auto control = box->addRow(
@@ -2688,6 +2716,14 @@ void EditCustomRankBox(
currentRank,
role),
style::margins());
Ui::AddSkip(box->verticalLayout());
Ui::AddDividerText(
box->verticalLayout(),
(isSelf
? tr::lng_rights_tag_about_self()
: tr::lng_rights_tag_about(
lt_name,
rpl::single(user->shortName()))));
const auto field = control->field();
box->setFocusCallback([=] { field->setFocusFast(); });
@@ -44,6 +44,7 @@ Fn<void(
Fn<void(
ChatRestrictionsInfo oldRights,
ChatRestrictionsInfo newRights)> SaveRestrictedCallback(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
not_null<PeerData*> participant,
Fn<void(ChatRestrictionsInfo newRights)> onDone,
@@ -203,7 +203,8 @@ private:
const std::unique_ptr<Ui::ChatStyle> _style;
const std::unique_ptr<TagPreviewDelegate> _delegate;
AdminLog::OwnedItem _item;
QPoint _position;
int _topSkip = 0;
int _bottomSkip = 0;
Ui::PeerUserpicView _userpic;
};
@@ -225,7 +226,8 @@ EditTagControl::PreviewWidget::PreviewWidget(
this,
_style.get(),
[=] { update(); }))
, _position(0, st::msgMargin.bottom()) {
, _topSkip(st::msgMargin.bottom() * 2)
, _bottomSkip(st::msgMargin.bottom() + st::msgMargin.top()) {
_style->apply(_theme.get());
_delegate->setTagText(initialText);
@@ -242,18 +244,18 @@ EditTagControl::PreviewWidget::PreviewWidget(
) | rpl::filter([=](int w) {
return w >= st::msgMinWidth;
}) | rpl::on_next([=](int w) {
const auto h = _position.y()
const auto h = _topSkip
+ _item->resizeGetHeight(w)
+ _position.y();
+ _bottomSkip;
resize(w, h);
}, lifetime());
_history->owner().itemResizeRequest(
) | rpl::on_next([=](not_null<const HistoryItem*> item) {
if (_item && item == _item->data() && width() >= st::msgMinWidth) {
const auto h = _position.y()
const auto h = _topSkip
+ _item->resizeGetHeight(width())
+ _position.y();
+ _bottomSkip;
resize(width(), h);
}
}, lifetime());
@@ -282,17 +284,26 @@ void EditTagControl::PreviewWidget::createItem() {
_item = std::move(owned);
applyBadge(_delegate->tagText());
if (width() >= st::msgMinWidth) {
const auto h = _position.y()
const auto h = _topSkip
+ _item->resizeGetHeight(width())
+ _position.y();
+ _bottomSkip;
resize(width(), h);
}
}
void EditTagControl::PreviewWidget::applyBadge(const QString &text) {
if (_item) {
_item->overrideRightBadge(text, _role);
if (!_item) {
return;
}
auto badgeText = text;
if (badgeText.isEmpty()) {
if (_role == HistoryView::BadgeRole::Admin) {
badgeText = tr::lng_admin_badge(tr::now);
} else if (_role == HistoryView::BadgeRole::Creator) {
badgeText = tr::lng_owner_badge(tr::now);
}
}
_item->overrideRightBadge(badgeText, _role);
}
void EditTagControl::PreviewWidget::setTagText(const QString &text) {
@@ -320,11 +331,12 @@ void EditTagControl::PreviewWidget::paintEvent(QPaintEvent *e) {
rect(),
e->rect(),
!window()->isActiveWindow());
p.translate(_position);
p.translate(0, _topSkip);
_item->draw(p, context);
if (_item->displayFromPhoto()) {
auto userpicBottom = height()
- _bottomSkip
- _item->marginBottom()
- _item->marginTop();
const auto userpicTop = userpicBottom - st::msgPhotoSize;
@@ -354,7 +366,11 @@ EditTagControl::EditTagControl(
, _field(Ui::CreateChild<Ui::InputField>(
this,
st::customBadgeField,
tr::lng_rights_edit_admin_rank_name(),
(role == HistoryView::BadgeRole::Admin
? tr::lng_admin_badge()
: role == HistoryView::BadgeRole::Creator
? tr::lng_owner_badge()
: tr::lng_rights_edit_admin_rank_name()),
TextUtilities::RemoveEmoji(currentRank))) {
_field->setMaxLength(kRankLimit);
_field->setInstantReplaces(Ui::InstantReplaces::TextOnly());
+1
View File
@@ -60,6 +60,7 @@ ChatAdminRightsInfo ChatData::defaultAdminRights(not_null<UserData*> user) {
| Flag::InviteByLinkOrAdd
| Flag::PinMessages
| Flag::ManageCall
| Flag::ManageRanks
| (isCreator ? Flag::AddAdmins : Flag(0)));
}
@@ -1692,6 +1692,7 @@ void InnerWidget::restrictParticipant(
restrictParticipantDone(participant, newRights);
};
const auto callback = SaveRestrictedCallback(
_controller->uiShow(),
_channel,
participant,
crl::guard(this, done),