diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e4df40ba68..9f2fe54dd3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -496,6 +496,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_profile_create_public_link" = "Create public link"; "lng_profile_edit_public_link" = "Edit public link"; "lng_profile_manage_admins" = "Manage administrators"; +"lng_profile_manage_blocklist" = "Manage blocked users"; "lng_profile_common_groups" = "{count:_not_used_|# group|# groups} in common"; "lng_profile_common_groups_section" = "Groups in common"; "lng_profile_participants_section" = "Members"; @@ -575,6 +576,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_channel_admin_sure" = "Add {user} to administrators?"; "lng_channel_admins_too_much" = "Sorry, you have reached the limit of the administrators. Please remove one administrator first."; +"lng_group_blocked_list_about" = "Blocked users are removed from the group and can only come back if invited by an admin.\nInvite links don't work for them."; + "lng_chat_all_members_admins" = "All Members Are Admins"; "lng_chat_about_all_admins" = "Group members can add new members, edit name and photo of the group."; "lng_chat_about_admins" = "Group admins can add and remove members, edit name and photo of the group."; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 17a438afe4..c7b01c0b95 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -300,6 +300,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt channel->setAbout(qs(f.vabout)); channel->setMembersCount(f.has_participants_count() ? f.vparticipants_count.v : 0); channel->setAdminsCount(f.has_admins_count() ? f.vadmins_count.v : 0); + channel->setKickedCount(f.has_kicked_count() ? f.vkicked_count.v : 0); channel->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString()); if (auto h = App::historyLoaded(channel->id)) { if (h->inboxReadBefore < f.vread_inbox_max_id.v + 1) { @@ -651,8 +652,10 @@ void ApiWrap::kickParticipant(PeerData *peer, UserData *user) { auto kick = KickRequest(peer, user); if (_kickRequests.contains(kick)) return; - if (peer->isChannel()) { - auto requestId = request(MTPchannels_KickFromChannel(peer->asChannel()->inputChannel, user->inputUser, MTP_bool(true))).done([this, peer, user](const MTPUpdates &result) { + if (auto channel = peer->asChannel()) { + auto requestId = request(MTPchannels_KickFromChannel(channel->inputChannel, user->inputUser, MTP_bool(true))).done([this, peer, user](const MTPUpdates &result) { + App::main()->sentUpdatesReceived(result); + _kickRequests.remove(KickRequest(peer, user)); if (auto channel = peer->asMegagroup()) { auto megagroupInfo = channel->mgInfo; @@ -668,6 +671,7 @@ void ApiWrap::kickParticipant(PeerData *peer, UserData *user) { megagroupInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; megagroupInfo->lastParticipantsCount = 0; } + channel->setKickedCount(channel->kickedCount() + 1); if (megagroupInfo->lastAdmins.contains(user)) { megagroupInfo->lastAdmins.remove(user); if (channel->adminsCount() > 1) { @@ -690,6 +694,30 @@ void ApiWrap::kickParticipant(PeerData *peer, UserData *user) { } } +void ApiWrap::unblockParticipant(PeerData *peer, UserData *user) { + auto kick = KickRequest(peer, user); + if (_kickRequests.contains(kick)) return; + + if (auto channel = peer->asChannel()) { + auto requestId = request(MTPchannels_KickFromChannel(channel->inputChannel, user->inputUser, MTP_bool(false))).done([this, peer, user](const MTPUpdates &result) { + App::main()->sentUpdatesReceived(result); + + _kickRequests.remove(KickRequest(peer, user)); + if (auto channel = peer->asMegagroup()) { + if (channel->kickedCount() > 0) { + channel->setKickedCount(channel->kickedCount() - 1); + } else { + channel->updateFull(true); + } + } + }).fail([this, kick](const RPCError &error) { + _kickRequests.remove(kick); + }).send(); + + _kickRequests.insert(kick, requestId); + } +} + void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) { if (!_stickerSetRequests.contains(setId)) { _stickerSetRequests.insert(setId, qMakePair(access, 0)); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 3b64d9a84a..1133f472c6 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -55,6 +55,7 @@ public: void requestSelfParticipant(ChannelData *channel); void kickParticipant(PeerData *peer, UserData *user); + void unblockParticipant(PeerData *peer, UserData *user); void requestWebPageDelayed(WebPageData *page); void clearWebPageRequest(WebPageData *page); diff --git a/Telegram/SourceFiles/boxes/members_box.cpp b/Telegram/SourceFiles/boxes/members_box.cpp index 1dcf814b61..50960c3544 100644 --- a/Telegram/SourceFiles/boxes/members_box.cpp +++ b/Telegram/SourceFiles/boxes/members_box.cpp @@ -514,80 +514,81 @@ void MembersBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names } void MembersBox::Inner::membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req) { + Expects(result.type() == mtpc_channels_channelParticipants); + clear(); _loadingRequestId = 0; - if (result.type() == mtpc_channels_channelParticipants) { - auto &d = result.c_channels_channelParticipants(); - auto &v = d.vparticipants.v; - _rows.reserve(v.size()); - _datas.reserve(v.size()); - _dates.reserve(v.size()); - _roles.reserve(v.size()); + auto &d = result.c_channels_channelParticipants(); + auto &v = d.vparticipants.v; + _rows.reserve(v.size()); + _datas.reserve(v.size()); + _dates.reserve(v.size()); + _roles.reserve(v.size()); - if (_filter == MembersFilter::Recent && _channel->membersCount() < d.vcount.v) { - _channel->setMembersCount(d.vcount.v); - if (App::main()) emit App::main()->peerUpdated(_channel); - } else if (_filter == MembersFilter::Admins && _channel->adminsCount() < d.vcount.v) { - _channel->setAdminsCount(d.vcount.v); - if (App::main()) emit App::main()->peerUpdated(_channel); + if (_filter == MembersFilter::Recent && _channel->membersCount() < d.vcount.v) { + _channel->setMembersCount(d.vcount.v); + if (App::main()) emit App::main()->peerUpdated(_channel); + } else if (_filter == MembersFilter::Admins && _channel->adminsCount() < d.vcount.v) { + _channel->setAdminsCount(d.vcount.v); + if (App::main()) emit App::main()->peerUpdated(_channel); + } + App::feedUsers(d.vusers); + + for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { + int32 userId = 0, addedTime = 0; + MemberRole role = MemberRole::None; + switch (i->type()) { + case mtpc_channelParticipant: + userId = i->c_channelParticipant().vuser_id.v; + addedTime = i->c_channelParticipant().vdate.v; + break; + case mtpc_channelParticipantSelf: + role = MemberRole::Self; + userId = i->c_channelParticipantSelf().vuser_id.v; + addedTime = i->c_channelParticipantSelf().vdate.v; + break; + case mtpc_channelParticipantModerator: + role = MemberRole::Moderator; + userId = i->c_channelParticipantModerator().vuser_id.v; + addedTime = i->c_channelParticipantModerator().vdate.v; + break; + case mtpc_channelParticipantEditor: + role = MemberRole::Editor; + userId = i->c_channelParticipantEditor().vuser_id.v; + addedTime = i->c_channelParticipantEditor().vdate.v; + break; + case mtpc_channelParticipantKicked: + userId = i->c_channelParticipantKicked().vuser_id.v; + addedTime = i->c_channelParticipantKicked().vdate.v; + role = MemberRole::Kicked; + break; + case mtpc_channelParticipantCreator: + userId = i->c_channelParticipantCreator().vuser_id.v; + addedTime = _channel->date; + role = MemberRole::Creator; + break; } - App::feedUsers(d.vusers); - - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - int32 userId = 0, addedTime = 0; - MemberRole role = MemberRole::None; - switch (i->type()) { - case mtpc_channelParticipant: - userId = i->c_channelParticipant().vuser_id.v; - addedTime = i->c_channelParticipant().vdate.v; - break; - case mtpc_channelParticipantSelf: - role = MemberRole::Self; - userId = i->c_channelParticipantSelf().vuser_id.v; - addedTime = i->c_channelParticipantSelf().vdate.v; - break; - case mtpc_channelParticipantModerator: - role = MemberRole::Moderator; - userId = i->c_channelParticipantModerator().vuser_id.v; - addedTime = i->c_channelParticipantModerator().vdate.v; - break; - case mtpc_channelParticipantEditor: - role = MemberRole::Editor; - userId = i->c_channelParticipantEditor().vuser_id.v; - addedTime = i->c_channelParticipantEditor().vdate.v; - break; - case mtpc_channelParticipantKicked: - userId = i->c_channelParticipantKicked().vuser_id.v; - addedTime = i->c_channelParticipantKicked().vdate.v; - role = MemberRole::Kicked; - break; - case mtpc_channelParticipantCreator: - userId = i->c_channelParticipantCreator().vuser_id.v; - addedTime = _channel->date; - role = MemberRole::Creator; - break; - } - if (UserData *user = App::userLoaded(userId)) { - _rows.push_back(user); - _dates.push_back(date(addedTime)); - _roles.push_back(role); - _datas.push_back(0); - } - } - - // update admins if we got all of them - if (_filter == MembersFilter::Admins && _channel->isMegagroup() && _rows.size() < Global::ChatSizeMax()) { - _channel->mgInfo->lastAdmins.clear(); - for (int32 i = 0, l = _rows.size(); i != l; ++i) { - if (_roles.at(i) == MemberRole::Creator || _roles.at(i) == MemberRole::Editor) { - _channel->mgInfo->lastAdmins.insert(_rows.at(i)); - } - } - - Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged); + if (UserData *user = App::userLoaded(userId)) { + _rows.push_back(user); + _dates.push_back(date(addedTime)); + _roles.push_back(role); + _datas.push_back(0); } } + + // update admins if we got all of them + if (_filter == MembersFilter::Admins && _channel->isMegagroup() && _rows.size() < Global::ChatSizeMax()) { + _channel->mgInfo->lastAdmins.clear(); + for (int32 i = 0, l = _rows.size(); i != l; ++i) { + if (_roles.at(i) == MemberRole::Creator || _roles.at(i) == MemberRole::Editor) { + _channel->mgInfo->lastAdmins.insert(_rows.at(i)); + } + } + + Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged); + } + if (_rows.isEmpty()) { _rows.push_back(App::self()); _dates.push_back(date(MTP_int(_channel->date))); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index a978dab7e1..a47afa0795 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -1052,8 +1052,11 @@ void PeerListBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) void PeerListBox::Inner::setSelected(Selected selected) { updateRow(_selected.index); - _selected = selected; - updateRow(_selected.index); + if (_selected != selected) { + _selected = selected; + updateRow(_selected.index); + setCursor(_selected.action ? style::cur_pointer : style::cur_default); + } } void PeerListBox::Inner::restoreSelection() { @@ -1069,7 +1072,7 @@ void PeerListBox::Inner::updateSelection() { auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePosition)); auto selected = Selected(); auto rowsPointY = point.y() - rowsTop; - selected.index.value = (in && rowsPointY >= 0) ? snap(rowsPointY / _rowHeight, 0, shownRowsCount() - 1) : -1; + selected.index.value = (in && rowsPointY >= 0 && rowsPointY < shownRowsCount() * _rowHeight) ? (rowsPointY / _rowHeight) : -1; if (selected.index.value >= 0) { auto row = getRow(selected.index); if (row->disabled()) { @@ -1086,10 +1089,7 @@ void PeerListBox::Inner::updateSelection() { } } } - if (_selected != selected) { - setSelected(selected); - setCursor(_selected.action ? style::cur_pointer : style::cur_default); - } + setSelected(selected); } void PeerListBox::Inner::peerUpdated(PeerData *peer) { diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h index 8d7002ce0b..da84ed8094 100644 --- a/Telegram/SourceFiles/observer_peer.h +++ b/Telegram/SourceFiles/observer_peer.h @@ -48,6 +48,7 @@ struct PeerUpdate { InviteLinkChanged = 0x00000100U, MembersChanged = 0x00000200U, AdminsChanged = 0x00000400U, + BlockedUsersChanged = 0x00000800U, // For users UserCanShareContact = 0x00010000U, diff --git a/Telegram/SourceFiles/profile/profile_block_channel_members.h b/Telegram/SourceFiles/profile/profile_block_channel_members.h index 8023265772..c7af60d755 100644 --- a/Telegram/SourceFiles/profile/profile_block_channel_members.h +++ b/Telegram/SourceFiles/profile/profile_block_channel_members.h @@ -42,7 +42,7 @@ protected: // Resizes content and counts natural widget height for the desired width. int resizeGetHeight(int newWidth) override; - private slots: +private slots: void onAdmins(); void onMembers(); diff --git a/Telegram/SourceFiles/profile/profile_block_settings.cpp b/Telegram/SourceFiles/profile/profile_block_settings.cpp index b0750b8f7c..537589fe65 100644 --- a/Telegram/SourceFiles/profile/profile_block_settings.cpp +++ b/Telegram/SourceFiles/profile/profile_block_settings.cpp @@ -25,7 +25,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/widgets/checkbox.h" #include "boxes/confirm_box.h" #include "boxes/contacts_box.h" +#include "boxes/peer_list_box.h" #include "observer_peer.h" +#include "auth_session.h" #include "mainwidget.h" #include "apiwrap.h" #include "lang.h" @@ -33,6 +35,126 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mainwindow.h" // tmp namespace Profile { +namespace { + +constexpr auto kBlockedPerPage = 40; + +class BlockedBoxController : public PeerListBox::Controller, private base::Subscriber, private MTP::Sender { +public: + BlockedBoxController(ChannelData *channel) : _channel(channel) { + } + + void prepare() override; + void rowClicked(PeerListBox::Row *row) override; + void rowActionClicked(PeerListBox::Row *row) override; + void preloadRows() override; + +private: + bool appendRow(UserData *user); + bool prependRow(UserData *user); + std::unique_ptr createRow(UserData *user) const; + + ChannelData *_channel = nullptr; + int _offset = 0; + mtpRequestId _loadRequestId = 0; + bool _allLoaded = false; + +}; + +void BlockedBoxController::prepare() { + view()->setTitle(lang(lng_blocked_list_title)); + view()->addButton(lang(lng_close), [this] { view()->closeBox(); }); + view()->setAboutText(lang(lng_contacts_loading)); + view()->refreshRows(); + + preloadRows(); +} + +void BlockedBoxController::preloadRows() { + if (_loadRequestId || _allLoaded) { + return; + } + + _loadRequestId = request(MTPchannels_GetParticipants(_channel->inputChannel, MTP_channelParticipantsKicked(), MTP_int(_offset), MTP_int(kBlockedPerPage))).done([this](const MTPchannels_ChannelParticipants &result) { + Expects(result.type() == mtpc_channels_channelParticipants); + + _loadRequestId = 0; + + if (!_offset) { + view()->setAboutText(lang(lng_group_blocked_list_about)); + } + auto &participants = result.c_channels_channelParticipants(); + App::feedUsers(participants.vusers); + + auto &list = participants.vparticipants.v; + if (list.isEmpty()) { + _allLoaded = true; + } else { + for_const (auto &participant, list) { + ++_offset; + if (participant.type() != mtpc_channelParticipantKicked) { + LOG(("API Error: Non kicked participant got while requesting for kicked participants: %1").arg(participant.type())); + continue; + } + auto &kicked = participant.c_channelParticipantKicked(); + auto userId = kicked.vuser_id.v; + if (auto user = App::userLoaded(userId)) { + appendRow(user); + } + } + } + view()->refreshRows(); + }).fail([this](const RPCError &error) { + _loadRequestId = 0; + }).send(); +} + +void BlockedBoxController::rowClicked(PeerListBox::Row *row) { + Ui::showPeerHistoryAsync(row->peer()->id, ShowAtUnreadMsgId); +} + +void BlockedBoxController::rowActionClicked(PeerListBox::Row *row) { + auto user = row->peer()->asUser(); + Expects(user != nullptr); + + view()->removeRow(row); + view()->refreshRows(); + + AuthSession::Current().api().unblockParticipant(_channel, user); +} + +bool BlockedBoxController::appendRow(UserData *user) { + if (view()->findRow(user)) { + return false; + } + view()->appendRow(createRow(user)); + return true; +} + +bool BlockedBoxController::prependRow(UserData *user) { + if (view()->findRow(user)) { + return false; + } + view()->prependRow(createRow(user)); + return true; +} + +std::unique_ptr BlockedBoxController::createRow(UserData *user) const { + auto row = std::make_unique(user); + row->setActionLink(lang(lng_blocked_list_unblock)); + auto status = [user]() -> QString { + if (user->botInfo) { + return lang(lng_status_bot); + } else if (user->phone().isEmpty()) { + return lang(lng_blocked_list_unknown_phone); + } + return App::formatPhone(user->phone()); + }; + row->setCustomStatus(status()); + return row; +} + +} // namespace using UpdateFlag = Notify::PeerUpdate::Flag; @@ -49,6 +171,7 @@ SettingsWidget::SettingsWidget(QWidget *parent, PeerData *peer) : BlockWidget(pa if (channel->amCreator()) { observeEvents |= UpdateFlag::UsernameChanged | UpdateFlag::InviteLinkChanged; } + observeEvents |= UpdateFlag::ChannelAmEditor | UpdateFlag::BlockedUsersChanged; } subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) { notifyPeerUpdated(update); @@ -74,6 +197,9 @@ void SettingsWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { if (update.flags & (UpdateFlag::ChatCanEdit)) { refreshManageAdminsButton(); } + if ((update.flags & UpdateFlag::ChannelAmEditor) || (update.flags & UpdateFlag::BlockedUsersChanged)) { + refreshManageBlockedUsersButton(); + } contentSizeUpdated(); } @@ -95,6 +221,7 @@ int SettingsWidget::resizeGetHeight(int newWidth) { newHeight += button->height(); }; moveLink(_manageAdmins); + moveLink(_manageBlockedUsers); moveLink(_inviteLink); newHeight += st::profileBlockMarginBottom; @@ -104,6 +231,7 @@ int SettingsWidget::resizeGetHeight(int newWidth) { void SettingsWidget::refreshButtons() { refreshEnableNotifications(); refreshManageAdminsButton(); + refreshManageBlockedUsersButton(); refreshInviteLinkButton(); } @@ -118,11 +246,11 @@ void SettingsWidget::refreshEnableNotifications() { } void SettingsWidget::refreshManageAdminsButton() { - auto hasManageAdmins = [this]() { + auto hasManageAdmins = [this] { if (auto chat = peer()->asChat()) { return (chat->amCreator() && chat->canEdit()); - } else if (auto channel = peer()->asChannel()) { - return (channel->amCreator() && channel->isMegagroup()); + } else if (auto channel = peer()->asMegagroup()) { + return channel->amCreator(); } return false; }; @@ -134,6 +262,21 @@ void SettingsWidget::refreshManageAdminsButton() { } } +void SettingsWidget::refreshManageBlockedUsersButton() { + auto hasManageBlockedUsers = [this] { + if (auto channel = peer()->asMegagroup()) { + return (channel->amCreator() || channel->amEditor()) && (channel->kickedCount() > 0); + } + return false; + }; + _manageBlockedUsers.destroy(); + if (hasManageBlockedUsers()) { + _manageBlockedUsers.create(this, lang(lng_profile_manage_blocklist), st::defaultLeftOutlineButton); + _manageBlockedUsers->show(); + connect(_manageBlockedUsers, SIGNAL(clicked()), this, SLOT(onManageBlockedUsers())); + } +} + void SettingsWidget::refreshInviteLinkButton() { auto getInviteLinkText = [this]() -> QString { if (auto chat = peer()->asChat()) { @@ -169,6 +312,12 @@ void SettingsWidget::onManageAdmins() { } } +void SettingsWidget::onManageBlockedUsers() { + if (auto channel = peer()->asMegagroup()) { + Ui::show(Box(std::make_unique(channel))); + } +} + void SettingsWidget::onInviteLink() { auto getInviteLink = [this]() { if (auto chat = peer()->asChat()) { diff --git a/Telegram/SourceFiles/profile/profile_block_settings.h b/Telegram/SourceFiles/profile/profile_block_settings.h index 86183564de..0c9fb74236 100644 --- a/Telegram/SourceFiles/profile/profile_block_settings.h +++ b/Telegram/SourceFiles/profile/profile_block_settings.h @@ -46,6 +46,7 @@ protected: private slots: void onNotificationsChange(); void onManageAdmins(); + void onManageBlockedUsers(); void onInviteLink(); private: @@ -55,6 +56,7 @@ private: void refreshButtons(); void refreshEnableNotifications(); void refreshManageAdminsButton(); + void refreshManageBlockedUsersButton(); void refreshInviteLinkButton(); object_ptr _enableNotifications; @@ -62,6 +64,7 @@ private: // In groups: creator of non-deactivated groups can see this link. // In channels: creator of supergroup can see this link. object_ptr _manageAdmins = { nullptr }; + object_ptr _manageBlockedUsers = { nullptr }; object_ptr _inviteLink = { nullptr }; }; diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 9476bbced9..825d1d0d8e 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -717,6 +717,13 @@ void ChannelData::setAdminsCount(int newAdminsCount) { } } +void ChannelData::setKickedCount(int newKickedCount) { + if (_kickedCount != newKickedCount) { + _kickedCount = newKickedCount; + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::BlockedUsersChanged); + } +} + void ChannelData::flagsUpdated() { if (isMegagroup()) { if (!mgInfo) { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index a797c8f4a4..8ef685f96e 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -740,6 +740,11 @@ public: } void setAdminsCount(int newAdminsCount); + int kickedCount() const { + return _kickedCount; + } + void setKickedCount(int newKickedCount); + int32 date = 0; int version = 0; MTPDchannel::Flags flags = { 0 }; @@ -880,6 +885,7 @@ private: int _membersCount = 1; int _adminsCount = 1; + int _kickedCount = 0; QString _restrictionReason; QString _about;