mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-01 21:56:58 +02:00
Feature/2865 participant sorting
This commit is contained in:
@@ -39,6 +39,8 @@
|
||||
NSString *currentSearchText;
|
||||
NSMutableArray<Contact*> *filteredActualParticipants;
|
||||
NSMutableArray<Contact*> *filteredInvitedParticipants;
|
||||
NSMutableArray<Contact*> *filteredAdminParticipants;
|
||||
NSMutableArray<Contact*> *filteredModParticipants;
|
||||
|
||||
// Mask view while processing a request
|
||||
UIActivityIndicatorView *pendingMaskSpinnerView;
|
||||
@@ -247,10 +249,14 @@
|
||||
|
||||
filteredActualParticipants = nil;
|
||||
filteredInvitedParticipants = nil;
|
||||
filteredAdminParticipants = nil;
|
||||
filteredModParticipants = nil;
|
||||
|
||||
actualParticipants = nil;
|
||||
invitedParticipants = nil;
|
||||
userParticipant = nil;
|
||||
adminParticipants = nil;
|
||||
modParticipants = nil;
|
||||
|
||||
[self removePendingActionMask];
|
||||
|
||||
@@ -580,6 +586,8 @@
|
||||
{
|
||||
actualParticipants = [NSMutableArray array];
|
||||
invitedParticipants = [NSMutableArray array];
|
||||
adminParticipants = [NSMutableArray array];
|
||||
modParticipants = [NSMutableArray array];
|
||||
userParticipant = nil;
|
||||
|
||||
if (self.mxRoom)
|
||||
@@ -596,21 +604,7 @@
|
||||
for (MXRoomMember *mxMember in members)
|
||||
{
|
||||
// Update the current participants list
|
||||
if ([mxMember.userId isEqualToString:userId])
|
||||
{
|
||||
if (mxMember.membership == MXMembershipJoin || mxMember.membership == MXMembershipInvite)
|
||||
{
|
||||
// The user is in this room
|
||||
NSString *displayName = [VectorL10n you];
|
||||
|
||||
self->userParticipant = [[Contact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:userId];
|
||||
self->userParticipant.mxMember = [roomState.members memberWithUserId:userId];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self handleRoomMember:mxMember];
|
||||
}
|
||||
[self handleRoomMember:mxMember];
|
||||
}
|
||||
|
||||
for (MXRoomThirdPartyInvite *roomThirdPartyInvite in roomThirdPartyInvites)
|
||||
@@ -648,10 +642,24 @@
|
||||
Contact *contact = [[Contact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:mxMember.userId];
|
||||
contact.mxMember = mxMember;
|
||||
|
||||
MXRoomState* roomState = self.mxRoom.dangerousSyncState;
|
||||
|
||||
// Update member power level
|
||||
MXRoomPowerLevels *powerLevels = [roomState powerLevels];
|
||||
NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:mxMember.userId];
|
||||
|
||||
RoomPowerLevel roomPowerLevel = [RoomPowerLevelHelper roomPowerLevelFrom:powerLevel];
|
||||
|
||||
if (mxMember.membership == MXMembershipInvite)
|
||||
{
|
||||
[invitedParticipants addObject:contact];
|
||||
}
|
||||
else if (roomPowerLevel == RoomPowerLevelAdmin) {
|
||||
[adminParticipants addObject:contact];
|
||||
}
|
||||
else if (roomPowerLevel == RoomPowerLevelModerator) {
|
||||
[modParticipants addObject:contact];
|
||||
}
|
||||
else
|
||||
{
|
||||
[actualParticipants addObject:contact];
|
||||
@@ -704,6 +712,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (adminParticipants.count)
|
||||
{
|
||||
for (index = 0; index < adminParticipants.count; index++)
|
||||
{
|
||||
Contact *contact = adminParticipants[index];
|
||||
|
||||
if (contact.mxMember && [contact.mxMember.userId isEqualToString:key])
|
||||
{
|
||||
[adminParticipants removeObjectAtIndex:index];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modParticipants.count)
|
||||
{
|
||||
for (index = 0; index < modParticipants.count; index++)
|
||||
{
|
||||
Contact *contact = modParticipants[index];
|
||||
|
||||
if (contact.mxMember && [contact.mxMember.userId isEqualToString:key])
|
||||
{
|
||||
[modParticipants removeObjectAtIndex:index];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (invitedParticipants.count)
|
||||
{
|
||||
for (index = 0; index < invitedParticipants.count; index++)
|
||||
@@ -732,6 +768,9 @@
|
||||
// ...and then alphabetically.
|
||||
// We could tiebreak instead by "last recently spoken in this room" if we wanted to.
|
||||
NSComparator comparator = ^NSComparisonResult(Contact *contactA, Contact *contactB) {
|
||||
if (BwiBuildSettings.bwiUserLabelParticipantSorting == true) {
|
||||
return [self bwiParticipantsCompareLeft:contactA right:contactB];
|
||||
}
|
||||
|
||||
MXUser *userA = [self.mxRoom.mxSession userWithUserId:contactA.mxMember.userId];
|
||||
MXUser *userB = [self.mxRoom.mxSession userWithUserId:contactB.mxMember.userId];
|
||||
@@ -748,7 +787,7 @@
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
|
||||
if (userA.currentlyActive && userB.currentlyActive)
|
||||
{
|
||||
// Order first by power levels (admins then moderators then others)
|
||||
@@ -806,6 +845,8 @@
|
||||
// Sort each participants list in alphabetical order
|
||||
[actualParticipants sortUsingComparator:comparator];
|
||||
[invitedParticipants sortUsingComparator:comparator];
|
||||
[adminParticipants sortUsingComparator:comparator];
|
||||
[modParticipants sortUsingComparator:comparator];
|
||||
|
||||
// Reload search result if any
|
||||
[self reloadSearchResult];
|
||||
@@ -916,10 +957,20 @@
|
||||
{
|
||||
NSInteger count = 0;
|
||||
|
||||
participantsSection = invitedSection = -1;
|
||||
participantsSection = invitedSection = adminSection = modSection = -1;
|
||||
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
if (filteredAdminParticipants.count)
|
||||
{
|
||||
adminSection = count++;
|
||||
}
|
||||
|
||||
if (filteredModParticipants.count)
|
||||
{
|
||||
modSection = count++;
|
||||
}
|
||||
|
||||
if (filteredActualParticipants.count)
|
||||
{
|
||||
participantsSection = count++;
|
||||
@@ -932,6 +983,14 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
if (adminParticipants.count) {
|
||||
adminSection = count++;
|
||||
}
|
||||
|
||||
if (modParticipants.count) {
|
||||
modSection = count++;
|
||||
}
|
||||
|
||||
if (userParticipant || actualParticipants.count)
|
||||
{
|
||||
participantsSection = count++;
|
||||
@@ -965,6 +1024,28 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (section == adminSection)
|
||||
{
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
count = filteredAdminParticipants.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = adminParticipants.count;
|
||||
}
|
||||
}
|
||||
else if (section == modSection)
|
||||
{
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
count = filteredModParticipants.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = modParticipants.count;
|
||||
}
|
||||
}
|
||||
else if (section == invitedSection)
|
||||
{
|
||||
if (currentSearchText.length)
|
||||
@@ -983,107 +1064,57 @@
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UITableViewCell *cell;
|
||||
|
||||
ContactTableViewCell* participantCell = [tableView dequeueReusableCellWithIdentifier:@"ParticipantTableViewCellId" forIndexPath:indexPath];
|
||||
participantCell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
participantCell.showCustomAccessoryView = self.showParticipantCustomAccessoryView;
|
||||
|
||||
if (indexPath.section == participantsSection || indexPath.section == invitedSection)
|
||||
participantCell.mxRoom = self.mxRoom;
|
||||
|
||||
Contact *contact = [self contactAtIndexPath:indexPath];
|
||||
|
||||
if (contact)
|
||||
{
|
||||
ContactTableViewCell* participantCell = [tableView dequeueReusableCellWithIdentifier:@"ParticipantTableViewCellId" forIndexPath:indexPath];
|
||||
participantCell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
participantCell.showCustomAccessoryView = self.showParticipantCustomAccessoryView;
|
||||
[participantCell render:contact];
|
||||
|
||||
participantCell.mxRoom = self.mxRoom;
|
||||
|
||||
Contact *contact;
|
||||
|
||||
if ((indexPath.section == participantsSection && userParticipant && indexPath.row == 0) && !currentSearchText.length)
|
||||
if (contact.mxMember)
|
||||
{
|
||||
// oneself dedicated cell
|
||||
contact = userParticipant;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSInteger index = indexPath.row;
|
||||
NSArray *participants;
|
||||
MXRoomState *roomState = self.mxRoom.dangerousSyncState;
|
||||
|
||||
if (indexPath.section == participantsSection)
|
||||
{
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
participants = filteredActualParticipants;
|
||||
}
|
||||
else
|
||||
{
|
||||
participants = actualParticipants;
|
||||
|
||||
if (userParticipant)
|
||||
{
|
||||
index --;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
participants = filteredInvitedParticipants;
|
||||
}
|
||||
else
|
||||
{
|
||||
participants = invitedParticipants;
|
||||
}
|
||||
// Update member power level
|
||||
MXRoomPowerLevels *powerLevels = [roomState powerLevels];
|
||||
NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:contact.mxMember.userId];
|
||||
|
||||
RoomPowerLevel roomPowerLevel = [RoomPowerLevelHelper roomPowerLevelFrom:powerLevel];
|
||||
|
||||
NSString *powerLevelText;
|
||||
|
||||
switch (roomPowerLevel) {
|
||||
case RoomPowerLevelAdmin:
|
||||
powerLevelText = [VectorL10n roomMemberPowerLevelShortAdmin];
|
||||
break;
|
||||
case RoomPowerLevelModerator:
|
||||
powerLevelText = [VectorL10n roomMemberPowerLevelShortModerator];
|
||||
break;
|
||||
default:
|
||||
powerLevelText = nil;
|
||||
break;
|
||||
}
|
||||
|
||||
if (index < participants.count)
|
||||
{
|
||||
contact = participants[index];
|
||||
}
|
||||
}
|
||||
|
||||
if (contact)
|
||||
{
|
||||
[participantCell render:contact];
|
||||
participantCell.powerLevelLabel.text = powerLevelText;
|
||||
|
||||
if (contact.mxMember)
|
||||
// Update the contact display name by considering the current room state.
|
||||
if (contact.mxMember.userId)
|
||||
{
|
||||
MXRoomState *roomState = self.mxRoom.dangerousSyncState;
|
||||
participantCell.contactDisplayNameLabel.text = [roomState.members memberName:contact.mxMember.userId];
|
||||
}
|
||||
|
||||
if (BwiBuildSettings.bwiUserLabelsParticipantsVisible) {
|
||||
contact.bwiUserLabel = [self.bwiUserLabelService getUserLabelWithUser:contact.mxMember.userId];
|
||||
|
||||
// Update member power level
|
||||
MXRoomPowerLevels *powerLevels = [roomState powerLevels];
|
||||
NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:contact.mxMember.userId];
|
||||
|
||||
RoomPowerLevel roomPowerLevel = [RoomPowerLevelHelper roomPowerLevelFrom:powerLevel];
|
||||
|
||||
NSString *powerLevelText;
|
||||
|
||||
switch (roomPowerLevel) {
|
||||
case RoomPowerLevelAdmin:
|
||||
powerLevelText = [VectorL10n roomMemberPowerLevelShortAdmin];
|
||||
break;
|
||||
case RoomPowerLevelModerator:
|
||||
powerLevelText = [VectorL10n roomMemberPowerLevelShortModerator];
|
||||
break;
|
||||
default:
|
||||
powerLevelText = nil;
|
||||
break;
|
||||
}
|
||||
|
||||
participantCell.powerLevelLabel.text = powerLevelText;
|
||||
|
||||
// Update the contact display name by considering the current room state.
|
||||
if (contact.mxMember.userId)
|
||||
{
|
||||
participantCell.contactDisplayNameLabel.text = [roomState.members memberName:contact.mxMember.userId];
|
||||
}
|
||||
|
||||
if (BwiBuildSettings.bwiUserLabelsParticipantsVisible) {
|
||||
contact.bwiUserLabel = [self.bwiUserLabelService getUserLabelWithUser:contact.mxMember.userId];
|
||||
|
||||
if (!participantCell.powerLevelLabel.text) {
|
||||
participantCell.powerLevelLabel.text = contact.bwiUserLabel;
|
||||
}
|
||||
}
|
||||
participantCell.powerLevelLabel.text = contact.bwiUserLabel;
|
||||
}
|
||||
}
|
||||
|
||||
cell = participantCell;
|
||||
}
|
||||
else
|
||||
@@ -1136,40 +1167,40 @@
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
CGFloat height = 0.0;
|
||||
|
||||
if (section == invitedSection)
|
||||
{
|
||||
height = 30.0;
|
||||
}
|
||||
|
||||
return height;
|
||||
return 30.0;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
UIView* sectionHeader;
|
||||
UIView* sectionHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 30)];
|
||||
sectionHeader.backgroundColor = ThemeService.shared.theme.headerBackgroundColor;
|
||||
|
||||
CGRect frame = sectionHeader.frame;
|
||||
frame.origin.x = 20;
|
||||
frame.origin.y = 5;
|
||||
frame.size.width = sectionHeader.frame.size.width - 10;
|
||||
frame.size.height -= 10;
|
||||
UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame];
|
||||
headerLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
|
||||
headerLabel.font = [UIFont boldSystemFontOfSize:15.0];
|
||||
headerLabel.backgroundColor = [UIColor clearColor];;
|
||||
|
||||
if (section == invitedSection)
|
||||
{
|
||||
sectionHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 30)];
|
||||
sectionHeader.backgroundColor = ThemeService.shared.theme.headerBackgroundColor;
|
||||
|
||||
CGRect frame = sectionHeader.frame;
|
||||
frame.origin.x = 20;
|
||||
frame.origin.y = 5;
|
||||
frame.size.width = sectionHeader.frame.size.width - 10;
|
||||
frame.size.height -= 10;
|
||||
UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame];
|
||||
headerLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
|
||||
headerLabel.font = [UIFont boldSystemFontOfSize:15.0];
|
||||
headerLabel.backgroundColor = [UIColor clearColor];
|
||||
|
||||
headerLabel.text = [VectorL10n roomParticipantsInvitedSection];
|
||||
|
||||
[sectionHeader addSubview:headerLabel];
|
||||
} else if (section == adminSection)
|
||||
{
|
||||
headerLabel.text = @"Admin";
|
||||
} else if (section == modSection)
|
||||
{
|
||||
headerLabel.text = @"Moderator";
|
||||
} else if (section == participantsSection)
|
||||
{
|
||||
headerLabel.text = @"Nutzer";
|
||||
}
|
||||
|
||||
[sectionHeader addSubview:headerLabel];
|
||||
|
||||
return sectionHeader;
|
||||
}
|
||||
|
||||
@@ -1186,51 +1217,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
Contact *contact;
|
||||
|
||||
// oneself dedicated cell
|
||||
if ((indexPath.section == participantsSection && userParticipant && indexPath.row == 0) && !currentSearchText.length)
|
||||
{
|
||||
contact = userParticipant;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSInteger index = indexPath.row;
|
||||
NSArray *participants;
|
||||
|
||||
if (indexPath.section == participantsSection)
|
||||
{
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
participants = filteredActualParticipants;
|
||||
}
|
||||
else
|
||||
{
|
||||
participants = actualParticipants;
|
||||
|
||||
if (userParticipant)
|
||||
{
|
||||
index --;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
participants = filteredInvitedParticipants;
|
||||
}
|
||||
else
|
||||
{
|
||||
participants = invitedParticipants;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < participants.count)
|
||||
{
|
||||
contact = participants[index];
|
||||
}
|
||||
}
|
||||
Contact *contact = [self contactAtIndexPath:indexPath];
|
||||
|
||||
if (contact.mxMember)
|
||||
{
|
||||
@@ -1552,6 +1539,8 @@
|
||||
// Copy participants and invited participants
|
||||
filteredActualParticipants = [NSMutableArray arrayWithArray:actualParticipants];
|
||||
filteredInvitedParticipants = [NSMutableArray arrayWithArray:invitedParticipants];
|
||||
filteredModParticipants = [NSMutableArray arrayWithArray:modParticipants];
|
||||
filteredAdminParticipants = [NSMutableArray arrayWithArray:adminParticipants];
|
||||
|
||||
// Add the current user if he belongs to the room members.
|
||||
if (userParticipant)
|
||||
@@ -1565,6 +1554,31 @@
|
||||
// Filter room participants
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
for (index = 0; index < filteredModParticipants.count;)
|
||||
{
|
||||
contact = filteredModParticipants[index];
|
||||
if (![contact matchedWithPatterns:@[currentSearchText]])
|
||||
{
|
||||
[filteredModParticipants removeObjectAtIndex:index];
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
for (index = 0; index < filteredAdminParticipants.count;)
|
||||
{
|
||||
contact = filteredAdminParticipants[index];
|
||||
if (![contact matchedWithPatterns:@[currentSearchText]])
|
||||
{
|
||||
[filteredAdminParticipants removeObjectAtIndex:index];
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
for (index = 0; index < filteredActualParticipants.count;)
|
||||
{
|
||||
contact = filteredActualParticipants[index];
|
||||
@@ -1595,6 +1609,8 @@
|
||||
{
|
||||
filteredActualParticipants = nil;
|
||||
filteredInvitedParticipants = nil;
|
||||
filteredModParticipants = nil;
|
||||
filteredAdminParticipants = nil;
|
||||
}
|
||||
|
||||
// Refresh display
|
||||
@@ -1656,4 +1672,102 @@
|
||||
[self removePendingActionMask];
|
||||
}
|
||||
|
||||
#pragma mark - bwi helper for admin and mod sections
|
||||
|
||||
- (Contact*)contactAtIndexPath:(NSIndexPath*)indexPath {
|
||||
Contact* contact;
|
||||
|
||||
if (indexPath.section == adminSection) {
|
||||
if (currentSearchText.length && indexPath.row < filteredAdminParticipants.count)
|
||||
{
|
||||
contact = filteredAdminParticipants[indexPath.row];
|
||||
} else if (indexPath.row < adminParticipants.count) {
|
||||
contact = adminParticipants[indexPath.row];
|
||||
}
|
||||
}
|
||||
if (indexPath.section == modSection) {
|
||||
if (currentSearchText.length && indexPath.row < filteredModParticipants.count)
|
||||
{
|
||||
contact = filteredModParticipants[indexPath.row];
|
||||
} else if (indexPath.row < modParticipants.count) {
|
||||
contact = modParticipants[indexPath.row];
|
||||
}
|
||||
}
|
||||
if (indexPath.section == participantsSection || indexPath.section == invitedSection)
|
||||
{
|
||||
if ((indexPath.section == participantsSection && userParticipant && indexPath.row == 0) && !currentSearchText.length)
|
||||
{
|
||||
// oneself dedicated cell
|
||||
contact = userParticipant;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSInteger index = indexPath.row;
|
||||
NSArray *participants;
|
||||
|
||||
if (indexPath.section == participantsSection)
|
||||
{
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
participants = filteredActualParticipants;
|
||||
}
|
||||
else
|
||||
{
|
||||
participants = actualParticipants;
|
||||
|
||||
if (userParticipant)
|
||||
{
|
||||
index --;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentSearchText.length)
|
||||
{
|
||||
participants = filteredInvitedParticipants;
|
||||
}
|
||||
else
|
||||
{
|
||||
participants = invitedParticipants;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < participants.count)
|
||||
{
|
||||
contact = participants[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return contact;
|
||||
}
|
||||
|
||||
- (NSComparisonResult)bwiParticipantsCompareLeft:(Contact*)contactA right:(Contact*)contactB {
|
||||
MXUser *userA = [self.mxRoom.mxSession userWithUserId:contactA.mxMember.userId];
|
||||
MXUser *userB = [self.mxRoom.mxSession userWithUserId:contactB.mxMember.userId];
|
||||
|
||||
if (!userA && !userB)
|
||||
{
|
||||
return [contactA.sortingDisplayName compare:contactB.sortingDisplayName options:NSCaseInsensitiveSearch];
|
||||
} else if (userA && !userB)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
} else if (!userA && userB)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
NSString* userLabelA = [self.bwiUserLabelService getUserLabelWithUser:contactA.mxMember.userId];
|
||||
NSString* userLabelB = [self.bwiUserLabelService getUserLabelWithUser:contactB.mxMember.userId];
|
||||
|
||||
if (userLabelA && !userLabelB) {
|
||||
return NSOrderedAscending;
|
||||
} else if (!userLabelA && userLabelB) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
return [contactA.sortingDisplayName compare:contactB.sortingDisplayName options:NSCaseInsensitiveSearch];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user