Allow creating approve-only invite links.

This commit is contained in:
John Preston 2021-10-12 12:39:07 +04:00
parent e471d61d7a
commit 3af3f85f82
20 changed files with 259 additions and 52 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -978,6 +978,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_removed_users" = "Removed users"; "lng_manage_peer_removed_users" = "Removed users";
"lng_manage_peer_permissions" = "Permissions"; "lng_manage_peer_permissions" = "Permissions";
"lng_manage_peer_invite_links" = "Invite links"; "lng_manage_peer_invite_links" = "Invite links";
"lng_manage_peer_requests" = "Member Requests";
"lng_manage_peer_requests_channel" = "Subscriber Requests";
"lng_manage_peer_group_type" = "Group type"; "lng_manage_peer_group_type" = "Group type";
"lng_manage_peer_channel_type" = "Channel type"; "lng_manage_peer_channel_type" = "Channel type";
@ -1158,8 +1160,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_user_left" = "{from} left the group"; "lng_action_user_left" = "{from} left the group";
"lng_action_user_joined" = "{from} joined the group"; "lng_action_user_joined" = "{from} joined the group";
"lng_action_user_joined_by_link" = "{from} joined the group via invite link"; "lng_action_user_joined_by_link" = "{from} joined the group via invite link";
"lng_action_user_joined_by_request" = "{from} joined the group by request"; "lng_action_user_joined_by_request" = "{from} was accepted to the group";
"lng_action_you_joined_by_request" = "Your request to join the group was approved"; "lng_action_you_joined_by_request" = "Your request to join the group was approved";
"lng_action_you_joined_by_request_channel" = "Your request to join the channel was approved";
"lng_action_user_registered" = "{from} just joined Telegram"; "lng_action_user_registered" = "{from} just joined Telegram";
"lng_action_removed_photo" = "{from} removed group photo"; "lng_action_removed_photo" = "{from} removed group photo";
"lng_action_removed_photo_channel" = "Channel photo removed"; "lng_action_removed_photo_channel" = "Channel photo removed";
@ -1271,6 +1274,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_joined#other" = "{count} joined"; "lng_group_invite_joined#other" = "{count} joined";
"lng_group_invite_remaining#one" = "{count} remaining"; "lng_group_invite_remaining#one" = "{count} remaining";
"lng_group_invite_remaining#other" = "{count} remaining"; "lng_group_invite_remaining#other" = "{count} remaining";
"lng_group_invite_requested#one" = "{count} requested";
"lng_group_invite_requested#other" = "{count} requested";
"lng_group_invite_requested_full#one" = "{count} requested to join";
"lng_group_invite_requested_full#other" = "{count} requested to join";
"lng_group_invite_can_join#one" = "{count} can join"; "lng_group_invite_can_join#one" = "{count} can join";
"lng_group_invite_can_join#other" = "{count} can join"; "lng_group_invite_can_join#other" = "{count} can join";
"lng_group_invite_days_left#one" = "{count} day left"; "lng_group_invite_days_left#one" = "{count} day left";
@ -1322,6 +1329,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_qr_title" = "Invite by QR Code"; "lng_group_invite_qr_title" = "Invite by QR Code";
"lng_group_invite_qr_about" = "Everyone on Telegram can scan this code to join your group."; "lng_group_invite_qr_about" = "Everyone on Telegram can scan this code to join your group.";
"lng_group_invite_qr_copied" = "QR Code copied to clipboard."; "lng_group_invite_qr_copied" = "QR Code copied to clipboard.";
"lng_group_invite_request_approve" = "Request admin approval";
"lng_group_invite_about_approve" = "New users will be able to join the group only after having been approved by the admins.";
"lng_group_invite_about_no_approve" = "New users will be able to join the group without being approved by the admins.";
"lng_group_invite_about_approve_channel" = "New users will be able to join the channel only after having been approved by the admins.";
"lng_group_invite_about_no_approve_channel" = "New users will be able to join the channel without being approved by the admins.";
"lng_group_request_to_join" = "Request to Join";
"lng_group_request_about" = "This group accepts new members only after they are approved by its admins.";
"lng_group_request_about_channel" = "This channel accepts new subscribers only after they are approved by its admins.";
"lng_group_request_sent" = "You will be added to the group once its admins approve your request.";
"lng_group_request_sent_channel" = "You will be added to the channel once its admins approve your request.";
"lng_group_requests_pending#one" = "{count} pending member request";
"lng_group_requests_pending#other" = "{count} pending member requests";
"lng_group_requests_pending_channel#one" = "{count} pending subscriber request";
"lng_group_requests_pending_channel#other" = "{count} pending subscriber requests";
"lng_group_requests_status" = "requested to join {date}";
"lng_group_requests_add" = "Add to Group";
"lng_group_requests_add_channel" = "Add to Channel";
"lng_group_requests_dismiss" = "Dismiss";
"lng_group_requests_was_added" = "{user} has been added to the group.";
"lng_group_requests_none" = "No member requests";
"lng_group_requests_none_channel" = "No subscriber requests";
"lng_channel_public_link_copied" = "Link copied to clipboard."; "lng_channel_public_link_copied" = "Link copied to clipboard.";
"lng_context_about_private_link" = "This link will only work for members of this chat."; "lng_context_about_private_link" = "This link will only work for members of this chat.";
@ -2497,6 +2526,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_edited_invite_link" = "edited invite link {link}"; "lng_admin_log_edited_invite_link" = "edited invite link {link}";
"lng_admin_log_invite_link_expire_date" = "Expire date: {previous} -> {limit}"; "lng_admin_log_invite_link_expire_date" = "Expire date: {previous} -> {limit}";
"lng_admin_log_invite_link_usage_limit" = "Usage limit: {previous} -> {limit}"; "lng_admin_log_invite_link_usage_limit" = "Usage limit: {previous} -> {limit}";
"lng_admin_log_invite_link_request_needed" = "Now admin approval is required to join.";
"lng_admin_log_invite_link_request_not_needed" = "Now admin approval is not required to join.";
"lng_admin_log_restricted_forever" = "indefinitely"; "lng_admin_log_restricted_forever" = "indefinitely";
"lng_admin_log_restricted_until" = "until {date}"; "lng_admin_log_restricted_until" = "until {date}";
"lng_admin_log_banned_view_messages" = "Read messages"; "lng_admin_log_banned_view_messages" = "Read messages";

View file

@ -70,8 +70,15 @@ void InviteLinks::create(
not_null<PeerData*> peer, not_null<PeerData*> peer,
Fn<void(Link)> done, Fn<void(Link)> done,
TimeId expireDate, TimeId expireDate,
int usageLimit) { int usageLimit,
performCreate(peer, std::move(done), false, expireDate, usageLimit); bool requestApproval) {
performCreate(
peer,
std::move(done),
false,
expireDate,
usageLimit,
requestApproval);
} }
void InviteLinks::performCreate( void InviteLinks::performCreate(
@ -79,7 +86,8 @@ void InviteLinks::performCreate(
Fn<void(Link)> done, Fn<void(Link)> done,
bool revokeLegacyPermanent, bool revokeLegacyPermanent,
TimeId expireDate, TimeId expireDate,
int usageLimit) { int usageLimit,
bool requestApproval) {
if (const auto i = _createCallbacks.find(peer) if (const auto i = _createCallbacks.find(peer)
; i != end(_createCallbacks)) { ; i != end(_createCallbacks)) {
if (done) { if (done) {
@ -98,7 +106,8 @@ void InviteLinks::performCreate(
? Flag::f_legacy_revoke_permanent ? Flag::f_legacy_revoke_permanent
: Flag(0)) : Flag(0))
| (expireDate ? Flag::f_expire_date : Flag(0)) | (expireDate ? Flag::f_expire_date : Flag(0))
| (usageLimit ? Flag::f_usage_limit : Flag(0))), | (usageLimit ? Flag::f_usage_limit : Flag(0))
| (requestApproval ? Flag::f_request_needed : Flag(0))),
peer->input, peer->input,
MTP_int(expireDate), MTP_int(expireDate),
MTP_int(usageLimit) MTP_int(usageLimit)
@ -201,6 +210,7 @@ void InviteLinks::edit(
const QString &link, const QString &link,
TimeId expireDate, TimeId expireDate,
int usageLimit, int usageLimit,
bool requestApproval,
Fn<void(Link)> done) { Fn<void(Link)> done) {
performEdit( performEdit(
peer, peer,
@ -209,7 +219,8 @@ void InviteLinks::edit(
std::move(done), std::move(done),
false, false,
expireDate, expireDate,
usageLimit); usageLimit,
requestApproval);
} }
void InviteLinks::performEdit( void InviteLinks::performEdit(
@ -219,7 +230,8 @@ void InviteLinks::performEdit(
Fn<void(Link)> done, Fn<void(Link)> done,
bool revoke, bool revoke,
TimeId expireDate, TimeId expireDate,
int usageLimit) { int usageLimit,
bool requestApproval) {
const auto key = LinkKey{ peer, link }; const auto key = LinkKey{ peer, link };
if (_deleteCallbacks.contains(key)) { if (_deleteCallbacks.contains(key)) {
return; return;
@ -239,12 +251,13 @@ void InviteLinks::performEdit(
_api->request(MTPmessages_EditExportedChatInvite( _api->request(MTPmessages_EditExportedChatInvite(
MTP_flags((revoke ? Flag::f_revoked : Flag(0)) MTP_flags((revoke ? Flag::f_revoked : Flag(0))
| (!revoke ? Flag::f_expire_date : Flag(0)) | (!revoke ? Flag::f_expire_date : Flag(0))
| (!revoke ? Flag::f_usage_limit : Flag(0))), | (!revoke ? Flag::f_usage_limit : Flag(0))
| (!revoke ? Flag::f_request_needed : Flag(0))),
peer->input, peer->input,
MTP_string(link), MTP_string(link),
MTP_int(expireDate), MTP_int(expireDate),
MTP_int(usageLimit), MTP_int(usageLimit),
MTPbool() // request_needed // #TODO requests MTP_bool(requestApproval)
)).done([=](const MTPmessages_ExportedChatInvite &result) { )).done([=](const MTPmessages_ExportedChatInvite &result) {
const auto callbacks = _editCallbacks.take(key); const auto callbacks = _editCallbacks.take(key);
const auto peer = key.peer; const auto peer = key.peer;
@ -632,6 +645,7 @@ auto InviteLinks::parse(
.expireDate = data.vexpire_date().value_or_empty(), .expireDate = data.vexpire_date().value_or_empty(),
.usageLimit = data.vusage_limit().value_or_empty(), .usageLimit = data.vusage_limit().value_or_empty(),
.usage = data.vusage().value_or_empty(), .usage = data.vusage().value_or_empty(),
.requestApproval = data.is_request_needed(),
.permanent = data.is_permanent(), .permanent = data.is_permanent(),
.revoked = data.is_revoked(), .revoked = data.is_revoked(),
}; };

View file

@ -19,6 +19,7 @@ struct InviteLink {
TimeId expireDate = 0; TimeId expireDate = 0;
int usageLimit = 0; int usageLimit = 0;
int usage = 0; int usage = 0;
bool requestApproval = false;
bool permanent = false; bool permanent = false;
bool revoked = false; bool revoked = false;
}; };
@ -61,13 +62,15 @@ public:
not_null<PeerData*> peer, not_null<PeerData*> peer,
Fn<void(Link)> done = nullptr, Fn<void(Link)> done = nullptr,
TimeId expireDate = 0, TimeId expireDate = 0,
int usageLimit = 0); int usageLimit = 0,
bool requestApproval = false);
void edit( void edit(
not_null<PeerData*> peer, not_null<PeerData*> peer,
not_null<UserData*> admin, not_null<UserData*> admin,
const QString &link, const QString &link,
TimeId expireDate, TimeId expireDate,
int usageLimit, int usageLimit,
bool requestApproval,
Fn<void(Link)> done = nullptr); Fn<void(Link)> done = nullptr);
void revoke( void revoke(
not_null<PeerData*> peer, not_null<PeerData*> peer,
@ -164,13 +167,15 @@ private:
Fn<void(Link)> done, Fn<void(Link)> done,
bool revoke, bool revoke,
TimeId expireDate = 0, TimeId expireDate = 0,
int usageLimit = 0); int usageLimit = 0,
bool requestApproval = false);
void performCreate( void performCreate(
not_null<PeerData*> peer, not_null<PeerData*> peer,
Fn<void(Link)> done, Fn<void(Link)> done,
bool revokeLegacyPermanent, bool revokeLegacyPermanent,
TimeId expireDate = 0, TimeId expireDate = 0,
int usageLimit = 0); int usageLimit = 0,
bool requestApproval = false);
void requestJoinedFirstSlice(LinkKey key); void requestJoinedFirstSlice(LinkKey key);
[[nodiscard]] std::optional<JoinedByLinkSlice> lookupJoinedFirstSlice( [[nodiscard]] std::optional<JoinedByLinkSlice> lookupJoinedFirstSlice(

View file

@ -1965,6 +1965,18 @@ void Updates::feedUpdate(const MTPUpdate &update) {
} }
} break; } break;
case mtpc_updatePendingJoinRequests: {
const auto &d = update.c_updatePendingJoinRequests();
if (const auto peer = session().data().peerLoaded(peerFromMTP(d.vpeer()))) {
const auto count = d.vrequests_pending().v;
if (const auto chat = peer->asChat()) {
chat->setPendingRequestsCount(count);
} else if (const auto channel = peer->asChannel()) {
channel->setPendingRequestsCount(count);
}
}
} break;
case mtpc_updateServiceNotification: { case mtpc_updateServiceNotification: {
const auto &d = update.c_updateServiceNotification(); const auto &d = update.c_updateServiceNotification();
const auto text = TextWithEntities { const auto text = TextWithEntities {

View file

@ -297,6 +297,7 @@ private:
void fillSignaturesButton(); void fillSignaturesButton();
void fillHistoryVisibilityButton(); void fillHistoryVisibilityButton();
void fillManageSection(); void fillManageSection();
void fillPendingRequestsButton();
void submitTitle(); void submitTitle();
void submitDescription(); void submitDescription();
@ -845,6 +846,8 @@ void Controller::fillHistoryVisibilityButton() {
void Controller::fillManageSection() { void Controller::fillManageSection() {
Expects(_controls.buttonsLayout != nullptr); Expects(_controls.buttonsLayout != nullptr);
using namespace rpl::mappers;
const auto chat = _peer->asChat(); const auto chat = _peer->asChat();
const auto channel = _peer->asChannel(); const auto channel = _peer->asChannel();
const auto isChannel = (!chat); const auto isChannel = (!chat);
@ -1042,6 +1045,9 @@ void Controller::fillManageSection() {
}, },
st::infoIconMembers); st::infoIconMembers);
} }
fillPendingRequestsButton();
if (canViewKicked) { if (canViewKicked) {
AddButtonWithCount( AddButtonWithCount(
_controls.buttonsLayout, _controls.buttonsLayout,
@ -1089,6 +1095,42 @@ void Controller::fillManageSection() {
} }
} }
void Controller::fillPendingRequestsButton() {
auto pendingRequestsCount = Info::Profile::MigratedOrMeValue(
_peer
) | rpl::map(
Info::Profile::PendingRequestsCountValue
) | rpl::flatten_latest(
) | rpl::start_spawning(_controls.buttonsLayout->lifetime());
const auto wrap = _controls.buttonsLayout->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_controls.buttonsLayout,
object_ptr<Ui::VerticalLayout>(
_controls.buttonsLayout)));
AddButtonWithCount(
wrap->entity(),
(_isGroup
? tr::lng_manage_peer_requests()
: tr::lng_manage_peer_requests_channel()),
rpl::duplicate(pendingRequestsCount) | ToPositiveNumberString(),
[=] {
_navigation->parentController()->show(
Box( // #TODO requests
ManageInviteLinksBox,
_peer,
_peer->session().user(),
0,
0),
Ui::LayerOption::KeepOther);
},
st::infoIconRequests);
std::move(
pendingRequestsCount
) | rpl::start_with_next([=](int count) {
wrap->toggle(count > 0, anim::type::instant);
}, wrap->lifetime());
}
void Controller::submitTitle() { void Controller::submitTitle() {
Expects(_controls.title != nullptr); Expects(_controls.title != nullptr);

View file

@ -943,7 +943,8 @@ void EditLink(
peer, peer,
finish, finish,
result.expireDate, result.expireDate,
result.usageLimit); result.usageLimit,
result.requestApproval);
} else { } else {
peer->session().api().inviteLinks().edit( peer->session().api().inviteLinks().edit(
peer, peer,
@ -951,18 +952,22 @@ void EditLink(
result.link, result.link,
result.expireDate, result.expireDate,
result.usageLimit, result.usageLimit,
result.requestApproval,
finish); finish);
} }
}; };
const auto isGroup = !peer->isBroadcast();
*box = Ui::show( *box = Ui::show(
(creating (creating
? Box(Ui::CreateInviteLinkBox, done) ? Box(Ui::CreateInviteLinkBox, isGroup, done)
: Box( : Box(
Ui::EditInviteLinkBox, Ui::EditInviteLinkBox,
Fields{ Fields{
.link = data.link, .link = data.link,
.expireDate = data.expireDate, .expireDate = data.expireDate,
.usageLimit = data.usageLimit .usageLimit = data.usageLimit,
.requestApproval = data.requestApproval,
.isGroup = isGroup,
}, },
done)), done)),
Ui::LayerOption::KeepOther); Ui::LayerOption::KeepOther);

View file

@ -50,51 +50,52 @@ struct NameUpdate {
}; };
struct PeerUpdate { struct PeerUpdate {
enum class Flag : uint32 { enum class Flag : uint64 {
None = 0, None = 0,
// Common flags // Common flags
Name = (1U << 0), Name = (1ULL << 0),
Username = (1U << 1), Username = (1ULL << 1),
Photo = (1U << 2), Photo = (1ULL << 2),
About = (1U << 3), About = (1ULL << 3),
Notifications = (1U << 4), Notifications = (1ULL << 4),
Migration = (1U << 5), Migration = (1ULL << 5),
UnavailableReason = (1U << 6), UnavailableReason = (1ULL << 6),
ChatThemeEmoji = (1U << 7), ChatThemeEmoji = (1ULL << 7),
IsBlocked = (1U << 8), IsBlocked = (1ULL << 8),
MessagesTTL = (1U << 9), MessagesTTL = (1ULL << 9),
// For users // For users
CanShareContact = (1U << 10), CanShareContact = (1ULL << 10),
IsContact = (1U << 11), IsContact = (1ULL << 11),
PhoneNumber = (1U << 12), PhoneNumber = (1ULL << 12),
OnlineStatus = (1U << 13), OnlineStatus = (1ULL << 13),
BotCommands = (1U << 14), BotCommands = (1ULL << 14),
BotCanBeInvited = (1U << 15), BotCanBeInvited = (1ULL << 15),
BotStartToken = (1U << 16), BotStartToken = (1ULL << 16),
CommonChats = (1U << 17), CommonChats = (1ULL << 17),
HasCalls = (1U << 18), HasCalls = (1ULL << 18),
SupportInfo = (1U << 19), SupportInfo = (1ULL << 19),
IsBot = (1U << 20), IsBot = (1ULL << 20),
// For chats and channels // For chats and channels
InviteLinks = (1U << 21), InviteLinks = (1ULL << 21),
Members = (1U << 22), Members = (1ULL << 22),
Admins = (1U << 23), Admins = (1ULL << 23),
BannedUsers = (1U << 24), BannedUsers = (1ULL << 24),
Rights = (1U << 25), Rights = (1ULL << 25),
PendingRequests = (1ULL << 26),
// For channels // For channels
ChannelAmIn = (1U << 26), ChannelAmIn = (1ULL << 27),
StickersSet = (1U << 27), StickersSet = (1ULL << 28),
ChannelLinkedChat = (1U << 28), ChannelLinkedChat = (1ULL << 29),
ChannelLocation = (1U << 29), ChannelLocation = (1ULL << 30),
Slowmode = (1U << 30), Slowmode = (1ULL << 31),
GroupCall = (1U << 31), GroupCall = (1ULL << 32),
// For iteration // For iteration
LastUsedBit = (1U << 31), LastUsedBit = (1ULL << 32),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -195,6 +195,13 @@ void ChannelData::setKickedCount(int newKickedCount) {
} }
} }
void ChannelData::setPendingRequestsCount(int count) {
if (_pendingRequestsCount != count) {
_pendingRequestsCount = count;
session().changes().peerUpdated(this, UpdateFlag::PendingRequests);
}
}
ChatRestrictionsInfo ChannelData::KickedRestrictedRights( ChatRestrictionsInfo ChannelData::KickedRestrictedRights(
not_null<PeerData*> participant) { not_null<PeerData*> participant) {
using Flag = ChatRestriction; using Flag = ChatRestriction;
@ -542,6 +549,9 @@ void ChannelData::setAdminRights(ChatAdminRights rights) {
return; return;
} }
_adminRights.set(rights); _adminRights.set(rights);
if (!canHaveInviteLink()) {
setPendingRequestsCount(0);
}
if (isMegagroup()) { if (isMegagroup()) {
const auto self = session().user(); const auto self = session().user();
if (hasAdminRights()) { if (hasAdminRights()) {
@ -874,6 +884,8 @@ void ApplyChannelUpdate(
} }
channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty())); channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
channel->fullUpdated(); channel->fullUpdated();
channel->setPendingRequestsCount(
update.vrequests_pending().value_or_empty());
if (canViewAdmins != channel->canViewAdmins() if (canViewAdmins != channel->canViewAdmins()
|| canViewMembers != channel->canViewMembers()) { || canViewMembers != channel->canViewMembers()) {

View file

@ -177,6 +177,11 @@ public:
} }
void setKickedCount(int newKickedCount); void setKickedCount(int newKickedCount);
[[nodiscard]] int pendingRequestsCount() const {
return _pendingRequestsCount;
}
void setPendingRequestsCount(int count);
[[nodiscard]] bool haveLeft() const { [[nodiscard]] bool haveLeft() const {
return flags() & Flag::Left; return flags() & Flag::Left;
} }
@ -426,6 +431,7 @@ private:
int _adminsCount = 1; int _adminsCount = 1;
int _restrictedCount = 0; int _restrictedCount = 0;
int _kickedCount = 0; int _kickedCount = 0;
int _pendingRequestsCount = 0;
MsgId _availableMinId = 0; MsgId _availableMinId = 0;
RestrictionFlags _defaultRestrictions; RestrictionFlags _defaultRestrictions;

View file

@ -146,6 +146,9 @@ void ChatData::setAdminRights(ChatAdminRights rights) {
return; return;
} }
_adminRights.set(rights); _adminRights.set(rights);
if (!canHaveInviteLink()) {
setPendingRequestsCount(0);
}
session().changes().peerUpdated( session().changes().peerUpdated(
this, this,
UpdateFlag::Rights | UpdateFlag::Admins | UpdateFlag::BannedUsers); UpdateFlag::Rights | UpdateFlag::Admins | UpdateFlag::BannedUsers);
@ -258,6 +261,13 @@ void ChatData::setBotCommands(
} }
} }
void ChatData::setPendingRequestsCount(int count) {
if (_pendingRequestsCount != count) {
_pendingRequestsCount = count;
session().changes().peerUpdated(this, UpdateFlag::PendingRequests);
}
}
namespace Data { namespace Data {
void ApplyChatUpdate( void ApplyChatUpdate(
@ -431,6 +441,8 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
chat->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty())); chat->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
chat->fullUpdated(); chat->fullUpdated();
chat->setAbout(qs(update.vabout())); chat->setAbout(qs(update.vabout()));
chat->setPendingRequestsCount(
update.vrequests_pending().value_or_empty());
chat->session().api().applyNotifySettings( chat->session().api().applyNotifySettings(
MTP_inputNotifyPeer(chat->input), MTP_inputNotifyPeer(chat->input),

View file

@ -164,6 +164,11 @@ public:
return _botCommands; return _botCommands;
} }
[[nodiscard]] int pendingRequestsCount() const {
return _pendingRequestsCount;
}
void setPendingRequestsCount(int count);
// Still public data members. // Still public data members.
const MTPlong inputChat; const MTPlong inputChat;
@ -185,6 +190,7 @@ private:
RestrictionFlags _defaultRestrictions; RestrictionFlags _defaultRestrictions;
AdminRightFlags _adminRights; AdminRightFlags _adminRights;
int _version = 0; int _version = 0;
int _pendingRequestsCount = 0;
std::unique_ptr<Data::GroupCall> _call; std::unique_ptr<Data::GroupCall> _call;
PeerId _callDefaultJoinAs = 0; PeerId _callDefaultJoinAs = 0;

View file

@ -312,6 +312,11 @@ TextWithEntities GenerateInviteLinkChangeText(
return data.vusage_limit().value_or_empty(); return data.vusage_limit().value_or_empty();
}); });
}; };
const auto requestApproval = [](const MTPExportedChatInvite &link) {
return link.match([](const MTPDchatInviteExported &data) {
return data.is_request_needed();
});
};
const auto wrapDate = [](TimeId date) { const auto wrapDate = [](TimeId date) {
return date return date
? langDateTime(base::unixtime::parse(date)) ? langDateTime(base::unixtime::parse(date))
@ -326,12 +331,17 @@ TextWithEntities GenerateInviteLinkChangeText(
const auto nowExpireDate = expireDate(newLink); const auto nowExpireDate = expireDate(newLink);
const auto wasUsageLimit = usageLimit(prevLink); const auto wasUsageLimit = usageLimit(prevLink);
const auto nowUsageLimit = usageLimit(newLink); const auto nowUsageLimit = usageLimit(newLink);
const auto wasRequestApproval = requestApproval(prevLink);
const auto nowRequestApproval = requestApproval(newLink);
if (wasExpireDate != nowExpireDate) { if (wasExpireDate != nowExpireDate) {
result.text.append('\n').append(tr::lng_admin_log_invite_link_expire_date(tr::now, lt_previous, wrapDate(wasExpireDate), lt_limit, wrapDate(nowExpireDate))); result.text.append('\n').append(tr::lng_admin_log_invite_link_expire_date(tr::now, lt_previous, wrapDate(wasExpireDate), lt_limit, wrapDate(nowExpireDate)));
} }
if (wasUsageLimit != nowUsageLimit) { if (wasUsageLimit != nowUsageLimit) {
result.text.append('\n').append(tr::lng_admin_log_invite_link_usage_limit(tr::now, lt_previous, wrapUsage(wasUsageLimit), lt_limit, wrapUsage(nowUsageLimit))); result.text.append('\n').append(tr::lng_admin_log_invite_link_usage_limit(tr::now, lt_previous, wrapUsage(wasUsageLimit), lt_limit, wrapUsage(nowUsageLimit)));
} }
if (wasRequestApproval != nowRequestApproval) {
result.text.append('\n').append(nowRequestApproval ? tr::lng_admin_log_invite_link_request_needed(tr::now) : tr::lng_admin_log_invite_link_request_not_needed(tr::now));
}
result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size())); result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size()));
return result; return result;

View file

@ -345,6 +345,7 @@ infoProfileSeparatorPadding: margins(
infoIconFg: menuIconFg; infoIconFg: menuIconFg;
infoIconInformation: icon {{ "info_information", infoIconFg }}; infoIconInformation: icon {{ "info_information", infoIconFg }};
infoIconMembers: icon {{ "info/edit/group_manage_members", infoIconFg, point(-2px, 0px) }}; infoIconMembers: icon {{ "info/edit/group_manage_members", infoIconFg, point(-2px, 0px) }};
infoIconRequests: icon {{ "info/edit/group_manage_requests", infoIconFg, point(-2px, 0px) }};
infoIconNotifications: icon {{ "info_notifications", infoIconFg }}; infoIconNotifications: icon {{ "info_notifications", infoIconFg }};
infoIconActions: icon {{ "info_actions", infoIconFg }}; infoIconActions: icon {{ "info_actions", infoIconFg }};
infoIconMediaPhoto: icon {{ "info_media_photo", infoIconFg }}; infoIconMediaPhoto: icon {{ "info_media_photo", infoIconFg }};

View file

@ -239,6 +239,25 @@ rpl::producer<int> MembersCountValue(not_null<PeerData*> peer) {
Unexpected("User in MembersCountViewer()."); Unexpected("User in MembersCountViewer().");
} }
rpl::producer<int> PendingRequestsCountValue(not_null<PeerData*> peer) {
if (const auto chat = peer->asChat()) {
return peer->session().changes().peerFlagsValue(
peer,
UpdateFlag::PendingRequests
) | rpl::map([=] {
return chat->pendingRequestsCount();
});
} else if (const auto channel = peer->asChannel()) {
return peer->session().changes().peerFlagsValue(
peer,
UpdateFlag::PendingRequests
) | rpl::map([=] {
return channel->pendingRequestsCount();
});
}
Unexpected("User in MembersCountViewer().");
}
rpl::producer<int> AdminsCountValue(not_null<PeerData*> peer) { rpl::producer<int> AdminsCountValue(not_null<PeerData*> peer) {
if (const auto chat = peer->asChat()) { if (const auto chat = peer->asChat()) {
return peer->session().changes().peerFlagsValue( return peer->session().changes().peerFlagsValue(

View file

@ -49,6 +49,7 @@ rpl::producer<bool> CanShareContactValue(not_null<UserData*> user);
rpl::producer<bool> CanAddContactValue(not_null<UserData*> user); rpl::producer<bool> CanAddContactValue(not_null<UserData*> user);
rpl::producer<bool> AmInChannelValue(not_null<ChannelData*> channel); rpl::producer<bool> AmInChannelValue(not_null<ChannelData*> channel);
rpl::producer<int> MembersCountValue(not_null<PeerData*> peer); rpl::producer<int> MembersCountValue(not_null<PeerData*> peer);
rpl::producer<int> PendingRequestsCountValue(not_null<PeerData*> peer);
rpl::producer<int> AdminsCountValue(not_null<PeerData*> peer); rpl::producer<int> AdminsCountValue(not_null<PeerData*> peer);
rpl::producer<int> RestrictionsCountValue(not_null<PeerData*> peer); rpl::producer<int> RestrictionsCountValue(not_null<PeerData*> peer);
rpl::producer<int> RestrictedCountValue(not_null<ChannelData*> channel); rpl::producer<int> RestrictedCountValue(not_null<ChannelData*> channel);

View file

@ -52,6 +52,7 @@ void EditInviteLinkBox(
const InviteLinkFields &data, const InviteLinkFields &data,
Fn<void(InviteLinkFields)> done) { Fn<void(InviteLinkFields)> done) {
const auto link = data.link; const auto link = data.link;
const auto isGroup = data.isGroup;
box->setTitle(link.isEmpty() box->setTitle(link.isEmpty()
? tr::lng_group_invite_new_title() ? tr::lng_group_invite_new_title()
: tr::lng_group_invite_edit_title()); : tr::lng_group_invite_edit_title());
@ -119,10 +120,12 @@ void EditInviteLinkBox(
Buttons usageButtons; Buttons usageButtons;
int expireValue = 0; int expireValue = 0;
int usageValue = 0; int usageValue = 0;
rpl::variable<bool> requestApproval = false;
}; };
const auto state = box->lifetime().make_state<State>(State{ const auto state = box->lifetime().make_state<State>(State{
.expireValue = expire, .expireValue = expire,
.usageValue = usage .usageValue = usage,
.requestApproval = data.requestApproval,
}); });
const auto regenerate = [=] { const auto regenerate = [=] {
expireGroup->setValue(state->expireValue); expireGroup->setValue(state->expireValue);
@ -260,6 +263,24 @@ void EditInviteLinkBox(
regenerate(); regenerate();
const auto buttonSkip = st::settingsSectionSkip;
const auto requestApproval = container->add(
object_ptr<SettingsButton>(
container,
tr::lng_group_invite_request_approve(),
st::settingsButton),
style::margins{ 0, buttonSkip, 0, buttonSkip });
requestApproval->toggleOn(state->requestApproval.value());
state->requestApproval = requestApproval->toggledValue();
addDivider(rpl::conditional(
state->requestApproval.value(),
(isGroup
? tr::lng_group_invite_about_approve()
: tr::lng_group_invite_about_approve_channel()),
(isGroup
? tr::lng_group_invite_about_no_approve()
: tr::lng_group_invite_about_no_approve_channel())));
const auto &saveLabel = link.isEmpty() const auto &saveLabel = link.isEmpty()
? tr::lng_formatting_link_create ? tr::lng_formatting_link_create
: tr::lng_settings_save; : tr::lng_settings_save;
@ -275,7 +296,9 @@ void EditInviteLinkBox(
done(InviteLinkFields{ done(InviteLinkFields{
.link = link, .link = link,
.expireDate = expireDate, .expireDate = expireDate,
.usageLimit = usageLimit .usageLimit = usageLimit,
.requestApproval = state->requestApproval.current(),
.isGroup = isGroup,
}); });
}); });
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
@ -283,8 +306,12 @@ void EditInviteLinkBox(
void CreateInviteLinkBox( void CreateInviteLinkBox(
not_null<GenericBox*> box, not_null<GenericBox*> box,
bool isGroup,
Fn<void(InviteLinkFields)> done) { Fn<void(InviteLinkFields)> done) {
EditInviteLinkBox(box, InviteLinkFields(), std::move(done)); EditInviteLinkBox(
box,
InviteLinkFields{ .isGroup = isGroup },
std::move(done));
} }
} // namespace Ui } // namespace Ui

View file

@ -15,6 +15,8 @@ struct InviteLinkFields {
QString link; QString link;
TimeId expireDate = 0; TimeId expireDate = 0;
int usageLimit = 0; int usageLimit = 0;
bool requestApproval = false;
bool isGroup = false;
}; };
void EditInviteLinkBox( void EditInviteLinkBox(
@ -24,6 +26,7 @@ void EditInviteLinkBox(
void CreateInviteLinkBox( void CreateInviteLinkBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
bool isGroup,
Fn<void(InviteLinkFields)> done); Fn<void(InviteLinkFields)> done);
} // namespace Ui } // namespace Ui