1584 lines
60 KiB
Objective-C
1584 lines
60 KiB
Objective-C
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
Copyright 2017 Vector Creations Ltd
|
|
Copyright 2015 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
#import "RoomParticipantsViewController.h"
|
|
|
|
#import "RoomMemberDetailsViewController.h"
|
|
|
|
#import "GeneratedInterface-Swift.h"
|
|
|
|
#import "Contact.h"
|
|
|
|
#import "MXCallManager.h"
|
|
|
|
#import "ContactTableViewCell.h"
|
|
|
|
#import "RageShakeManager.h"
|
|
|
|
@interface RoomParticipantsViewController () <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UIGestureRecognizerDelegate, MXKRoomMemberDetailsViewControllerDelegate, RoomParticipantsInviteCoordinatorBridgePresenterDelegate>
|
|
{
|
|
// Search result
|
|
NSString *currentSearchText;
|
|
NSMutableArray<Contact*> *filteredActualParticipants;
|
|
NSMutableArray<Contact*> *filteredInvitedParticipants;
|
|
|
|
// Mask view while processing a request
|
|
UIActivityIndicatorView *pendingMaskSpinnerView;
|
|
|
|
// The members events listener.
|
|
id membersListener;
|
|
|
|
// Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
|
|
id leaveRoomNotificationObserver;
|
|
|
|
// Observe kMXRoomDidFlushDataNotification to take into account the updated room members when the room history is flushed.
|
|
id roomDidFlushDataNotificationObserver;
|
|
|
|
RoomMemberDetailsViewController *memberDetailsViewController;
|
|
|
|
UIAlertController *currentAlert;
|
|
|
|
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
|
|
id kThemeServiceDidChangeThemeNotificationObserver;
|
|
|
|
RoomParticipantsInviteCoordinatorBridgePresenter *invitePresenter;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RoomParticipantsViewController
|
|
|
|
#pragma mark - Class methods
|
|
|
|
+ (UINib *)nib
|
|
{
|
|
return [UINib nibWithNibName:NSStringFromClass([RoomParticipantsViewController class])
|
|
bundle:[NSBundle bundleForClass:[RoomParticipantsViewController class]]];
|
|
}
|
|
|
|
+ (instancetype)roomParticipantsViewController
|
|
{
|
|
return [[[self class] alloc] initWithNibName:NSStringFromClass([RoomParticipantsViewController class])
|
|
bundle:[NSBundle bundleForClass:[RoomParticipantsViewController class]]];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)finalizeInit
|
|
{
|
|
[super finalizeInit];
|
|
|
|
// Setup `MXKViewControllerHandling` properties
|
|
self.enableBarTintColorStatusChange = NO;
|
|
self.rageShakeManager = [RageShakeManager sharedManager];
|
|
self.showParticipantCustomAccessoryView = YES;
|
|
self.showInviteUserFab = YES;
|
|
}
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
[super viewDidLoad];
|
|
// Do any additional setup after loading the view, typically from a nib.
|
|
|
|
// Check whether the view controller has been pushed via storyboard
|
|
if (!self.tableView)
|
|
{
|
|
// Instantiate view controller objects
|
|
[[[self class] nib] instantiateWithOwner:self options:nil];
|
|
}
|
|
|
|
// Adjust Top and Bottom constraints to take into account potential navBar and tabBar.
|
|
[NSLayoutConstraint deactivateConstraints:@[_searchBarTopConstraint]];
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated"
|
|
_searchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide
|
|
attribute:NSLayoutAttributeBottom
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.searchBarHeader
|
|
attribute:NSLayoutAttributeTop
|
|
multiplier:1.0f
|
|
constant:0.0f];
|
|
#pragma clang diagnostic pop
|
|
|
|
[NSLayoutConstraint activateConstraints:@[_searchBarTopConstraint]];
|
|
|
|
self.navigationItem.title = [VectorL10n roomParticipantsTitle];
|
|
|
|
if (self.mxRoom.summary.roomType == MXRoomTypeSpace)
|
|
{
|
|
_searchBarView.placeholder = [VectorL10n searchDefaultPlaceholder];
|
|
}
|
|
else if (self.mxRoom.isDirect)
|
|
{
|
|
_searchBarView.placeholder = [VectorL10n roomParticipantsFilterRoomMembersForDm];
|
|
}
|
|
else
|
|
{
|
|
_searchBarView.placeholder = [VectorL10n roomParticipantsFilterRoomMembers];
|
|
}
|
|
_searchBarView.returnKeyType = UIReturnKeyDone;
|
|
_searchBarView.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
|
|
|
// Search bar header is hidden when no room is provided
|
|
_searchBarHeader.hidden = (self.mxRoom == nil);
|
|
|
|
[self setNavBarButtons];
|
|
|
|
// Hide line separators of empty cells
|
|
self.tableView.tableFooterView = [[UIView alloc] init];
|
|
|
|
[self.tableView registerClass:ContactTableViewCell.class forCellReuseIdentifier:@"ParticipantTableViewCellId"];
|
|
|
|
|
|
if (_showInviteUserFab)
|
|
{
|
|
// Add invite members button programmatically
|
|
[self vc_addFABWithImage:AssetImages.addMemberFloatingAction.image
|
|
target:self
|
|
action:@selector(onAddParticipantButtonPressed)];
|
|
}
|
|
|
|
// Observe user interface theme change.
|
|
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
[self userInterfaceThemeDidChange];
|
|
|
|
}];
|
|
[self userInterfaceThemeDidChange];
|
|
}
|
|
|
|
- (void)userInterfaceThemeDidChange
|
|
{
|
|
[ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar];
|
|
|
|
self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor;
|
|
|
|
[self refreshSearchBarItemsColor:_searchBarView];
|
|
|
|
_searchBarHeaderBorder.backgroundColor = ThemeService.shared.theme.headerBorderColor;
|
|
|
|
// Check the table view style to select its bg color.
|
|
self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor);
|
|
self.view.backgroundColor = self.tableView.backgroundColor;
|
|
self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor;
|
|
|
|
if (self.tableView.dataSource)
|
|
{
|
|
[self.tableView reloadData];
|
|
}
|
|
|
|
[self setNeedsStatusBarAppearanceUpdate];
|
|
}
|
|
|
|
- (UIStatusBarStyle)preferredStatusBarStyle
|
|
{
|
|
return ThemeService.shared.theme.statusBarStyle;
|
|
}
|
|
|
|
// This method is called when the viewcontroller is added or removed from a container view controller.
|
|
- (void)didMoveToParentViewController:(nullable UIViewController *)parent
|
|
{
|
|
[super didMoveToParentViewController:parent];
|
|
|
|
[self setNavBarButtons];
|
|
}
|
|
|
|
- (void)destroy
|
|
{
|
|
if (kThemeServiceDidChangeThemeNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver];
|
|
kThemeServiceDidChangeThemeNotificationObserver = nil;
|
|
}
|
|
|
|
if (leaveRoomNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:leaveRoomNotificationObserver];
|
|
leaveRoomNotificationObserver = nil;
|
|
}
|
|
|
|
if (roomDidFlushDataNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:roomDidFlushDataNotificationObserver];
|
|
roomDidFlushDataNotificationObserver = nil;
|
|
}
|
|
|
|
if (membersListener)
|
|
{
|
|
MXWeakify(self);
|
|
[self.mxRoom liveTimeline:^(id<MXEventTimeline> liveTimeline) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[liveTimeline removeListener:self->membersListener];
|
|
self->membersListener = nil;
|
|
}];
|
|
}
|
|
|
|
if (currentAlert)
|
|
{
|
|
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
|
currentAlert = nil;
|
|
}
|
|
|
|
_mxRoom = nil;
|
|
|
|
filteredActualParticipants = nil;
|
|
filteredInvitedParticipants = nil;
|
|
|
|
actualParticipants = nil;
|
|
invitedParticipants = nil;
|
|
userParticipant = nil;
|
|
|
|
[self removePendingActionMask];
|
|
|
|
[super destroy];
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
{
|
|
[super viewWillAppear:animated];
|
|
|
|
// Refresh display
|
|
[self refreshTableView];
|
|
|
|
[self.screenTracker trackScreen];
|
|
}
|
|
|
|
- (void)viewDidAppear:(BOOL)animated
|
|
{
|
|
[super viewDidAppear:animated];
|
|
|
|
if (memberDetailsViewController)
|
|
{
|
|
[memberDetailsViewController destroy];
|
|
memberDetailsViewController = nil;
|
|
}
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated
|
|
{
|
|
[super viewWillDisappear:animated];
|
|
|
|
if (currentAlert)
|
|
{
|
|
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
|
currentAlert = nil;
|
|
}
|
|
|
|
// cancel any pending search
|
|
[self searchBarCancelButtonClicked:_searchBarView];
|
|
}
|
|
|
|
- (void)withdrawViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion
|
|
{
|
|
// Check whether the current view controller is displayed inside a segmented view controller in order to withdraw the right item
|
|
if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class])
|
|
{
|
|
[((SegmentedViewController*)self.parentViewController) withdrawViewControllerAnimated:animated completion:completion];
|
|
}
|
|
else
|
|
{
|
|
[super withdrawViewControllerAnimated:animated completion:completion];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)setMxRoom:(MXRoom *)mxRoom
|
|
{
|
|
// Cancel any pending search
|
|
[self searchBarCancelButtonClicked:_searchBarView];
|
|
|
|
// Make sure we can access synchronously to self.mxRoom and mxRoom data
|
|
// to avoid race conditions
|
|
MXWeakify(self);
|
|
[mxRoom.mxSession preloadRoomsData:_mxRoom ? @[_mxRoom.roomId, mxRoom.roomId] : @[mxRoom.roomId]
|
|
onComplete:^{
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
// Remove previous room registration (if any).
|
|
if (self.mxRoom)
|
|
{
|
|
// Remove the previous listener
|
|
if (self->leaveRoomNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self->leaveRoomNotificationObserver];
|
|
self->leaveRoomNotificationObserver = nil;
|
|
}
|
|
if (self->roomDidFlushDataNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self->roomDidFlushDataNotificationObserver];
|
|
self->roomDidFlushDataNotificationObserver = nil;
|
|
}
|
|
if (self->membersListener)
|
|
{
|
|
MXWeakify(self);
|
|
[self.mxRoom liveTimeline:^(id<MXEventTimeline> liveTimeline) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[liveTimeline removeListener:self->membersListener];
|
|
self->membersListener = nil;
|
|
}];
|
|
}
|
|
|
|
[self removeMatrixSession:self.mxRoom.mxSession];
|
|
}
|
|
|
|
self->_mxRoom = mxRoom;
|
|
|
|
if (self.mxRoom)
|
|
{
|
|
self.searchBarHeader.hidden = NO;
|
|
|
|
if (self.mxRoom.summary.roomType == MXRoomTypeSpace)
|
|
{
|
|
self.searchBarView.placeholder = [VectorL10n searchDefaultPlaceholder];
|
|
}
|
|
else if (self.mxRoom.isDirect)
|
|
{
|
|
self.searchBarView.placeholder = [VectorL10n roomParticipantsFilterRoomMembersForDm];
|
|
}
|
|
else
|
|
{
|
|
self.searchBarView.placeholder = [VectorL10n roomParticipantsFilterRoomMembers];
|
|
}
|
|
|
|
// Update the current matrix session.
|
|
[self addMatrixSession:self.mxRoom.mxSession];
|
|
|
|
// Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
|
|
self->leaveRoomNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionWillLeaveRoomNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
// Check whether the user will leave the room related to the displayed participants
|
|
if (notif.object == self.mxRoom.mxSession)
|
|
{
|
|
NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
|
|
if (roomId && [roomId isEqualToString:self.mxRoom.roomId])
|
|
{
|
|
// We remove the current view controller.
|
|
[self withdrawViewControllerAnimated:YES completion:nil];
|
|
}
|
|
}
|
|
}];
|
|
|
|
// Observe room history flush (sync with limited timeline, or state event redaction)
|
|
self->roomDidFlushDataNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomDidFlushDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
MXRoom *room = notif.object;
|
|
if (self.mxRoom.mxSession == room.mxSession && [self.mxRoom.roomId isEqualToString:room.roomId])
|
|
{
|
|
// The existing room history has been flushed during server sync. Take into account the updated room members list.
|
|
[self refreshParticipantsFromRoomMembers];
|
|
|
|
[self refreshTableView];
|
|
}
|
|
|
|
}];
|
|
|
|
// Register a listener for events that concern room members
|
|
NSArray *mxMembersEvents = @[kMXEventTypeStringRoomMember, kMXEventTypeStringRoomThirdPartyInvite, kMXEventTypeStringRoomPowerLevels];
|
|
|
|
MXWeakify(self);
|
|
[self.mxRoom liveTimeline:^(id<MXEventTimeline> liveTimeline) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
self->membersListener = [liveTimeline listenToEventsOfTypes:mxMembersEvents onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) {
|
|
|
|
// Consider only live event
|
|
if (direction == MXTimelineDirectionForwards)
|
|
{
|
|
switch (event.eventType)
|
|
{
|
|
case MXEventTypeRoomMember:
|
|
{
|
|
// Take into account updated member
|
|
// Ignore here change related to the current user (this change is handled by leaveRoomNotificationObserver)
|
|
if ([event.stateKey isEqualToString:self.mxRoom.mxSession.myUser.userId] == NO)
|
|
{
|
|
MXRoomMember *mxMember = [liveTimeline.state.members memberWithUserId:event.stateKey];
|
|
if (mxMember)
|
|
{
|
|
// Remove previous occurrence of this member (if any)
|
|
[self removeParticipantByKey:mxMember.userId];
|
|
|
|
// If any, remove 3pid invite corresponding to this room member
|
|
if (mxMember.thirdPartyInviteToken)
|
|
{
|
|
[self removeParticipantByKey:mxMember.thirdPartyInviteToken];
|
|
}
|
|
|
|
[self handleRoomMember:mxMember];
|
|
|
|
[self finalizeParticipantsList:liveTimeline.state];
|
|
|
|
[self refreshTableView];
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MXEventTypeRoomThirdPartyInvite:
|
|
{
|
|
MXRoomThirdPartyInvite *thirdPartyInvite = [liveTimeline.state thirdPartyInviteWithToken:event.stateKey];
|
|
if (thirdPartyInvite)
|
|
{
|
|
[self addRoomThirdPartyInviteToParticipants:thirdPartyInvite roomState:liveTimeline.state];
|
|
|
|
[self finalizeParticipantsList:liveTimeline.state];
|
|
|
|
[self refreshTableView];
|
|
}
|
|
break;
|
|
}
|
|
case MXEventTypeRoomPowerLevels:
|
|
{
|
|
[self refreshParticipantsFromRoomMembers];
|
|
|
|
[self refreshTableView];
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}];
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
// Search bar header is hidden when no room is provided
|
|
self.searchBarHeader.hidden = YES;
|
|
}
|
|
|
|
// Refresh the members list.
|
|
[self refreshParticipantsFromRoomMembers];
|
|
|
|
[self refreshTableView];
|
|
}];
|
|
}
|
|
|
|
- (void)setEnableMention:(BOOL)enableMention
|
|
{
|
|
if (_enableMention != enableMention)
|
|
{
|
|
_enableMention = enableMention;
|
|
|
|
if (memberDetailsViewController)
|
|
{
|
|
memberDetailsViewController.enableMention = enableMention;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)startActivityIndicator
|
|
{
|
|
// Check whether the current view controller is displayed inside a segmented view controller in order to run the right activity view
|
|
if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class])
|
|
{
|
|
[((SegmentedViewController*)self.parentViewController) startActivityIndicator];
|
|
|
|
// Force stop the activity view of the view controller
|
|
[self.activityIndicator stopAnimating];
|
|
}
|
|
else
|
|
{
|
|
[super startActivityIndicator];
|
|
}
|
|
}
|
|
|
|
- (void)stopActivityIndicator
|
|
{
|
|
// Check whether the current view controller is displayed inside a segmented view controller in order to stop the right activity view
|
|
if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class])
|
|
{
|
|
[((SegmentedViewController*)self.parentViewController) stopActivityIndicator];
|
|
|
|
// Force stop the activity view of the view controller
|
|
[self.activityIndicator stopAnimating];
|
|
}
|
|
else
|
|
{
|
|
[super stopActivityIndicator];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Internals
|
|
|
|
- (void)refreshTableView
|
|
{
|
|
[self.tableView reloadData];
|
|
}
|
|
|
|
- (void)setNavBarButtons
|
|
{
|
|
// Check whether the view controller is currently displayed inside a segmented view controller or not.
|
|
UIViewController* topViewController = ((self.parentViewController) ? self.parentViewController : self);
|
|
topViewController.navigationItem.rightBarButtonItem = nil;
|
|
|
|
if (self.showCancelBarButtonItem)
|
|
{
|
|
topViewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(onCancel:)];
|
|
}
|
|
else
|
|
{
|
|
topViewController.navigationItem.leftBarButtonItem = nil;
|
|
}
|
|
}
|
|
|
|
- (void)onAddParticipantButtonPressed
|
|
{
|
|
self->invitePresenter = [[RoomParticipantsInviteCoordinatorBridgePresenter alloc] initWithSession:self.mxRoom.mxSession room:self.mxRoom parentSpaceId:self.parentSpaceId currentSearchText:currentSearchText actualParticipants:actualParticipants invitedParticipants:invitedParticipants userParticipant:userParticipant];
|
|
self->invitePresenter.delegate = self;
|
|
[self->invitePresenter presentFrom:self animated:true];
|
|
}
|
|
|
|
- (void)refreshParticipantsFromRoomMembers
|
|
{
|
|
actualParticipants = [NSMutableArray array];
|
|
invitedParticipants = [NSMutableArray array];
|
|
userParticipant = nil;
|
|
|
|
if (self.mxRoom)
|
|
{
|
|
// Retrieve the current members from the room state
|
|
MXWeakify(self);
|
|
[self.mxRoom state:^(MXRoomState *roomState) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
NSArray *members = [roomState.members membersWithoutConferenceUser];
|
|
NSString *userId = self.mxRoom.mxSession.myUser.userId;
|
|
NSArray *roomThirdPartyInvites = roomState.thirdPartyInvites;
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
for (MXRoomThirdPartyInvite *roomThirdPartyInvite in roomThirdPartyInvites)
|
|
{
|
|
[self addRoomThirdPartyInviteToParticipants:roomThirdPartyInvite roomState:roomState];
|
|
}
|
|
|
|
[self finalizeParticipantsList:roomState];
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)handleRoomMember:(MXRoomMember*)mxMember
|
|
{
|
|
// Add this member after checking his status
|
|
if (mxMember.membership == MXMembershipJoin || mxMember.membership == MXMembershipInvite)
|
|
{
|
|
// Prepare the display name of this member
|
|
NSString *displayName = mxMember.displayname;
|
|
if (displayName.length == 0)
|
|
{
|
|
// Look for the corresponding MXUser in matrix session
|
|
MXUser *mxUser = [self.mxRoom.mxSession userWithUserId:mxMember.userId];
|
|
if (mxUser)
|
|
{
|
|
displayName = ((mxUser.displayname.length > 0) ? mxUser.displayname : mxMember.userId);
|
|
}
|
|
else
|
|
{
|
|
displayName = mxMember.userId;
|
|
}
|
|
}
|
|
|
|
// Create the contact related to this member
|
|
Contact *contact = [[Contact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:mxMember.userId];
|
|
contact.mxMember = mxMember;
|
|
|
|
if (mxMember.membership == MXMembershipInvite)
|
|
{
|
|
[invitedParticipants addObject:contact];
|
|
}
|
|
else
|
|
{
|
|
[actualParticipants addObject:contact];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)reloadSearchResult
|
|
{
|
|
if (currentSearchText.length)
|
|
{
|
|
NSString *searchText = currentSearchText;
|
|
currentSearchText = nil;
|
|
|
|
[self searchBar:_searchBarView textDidChange:searchText];
|
|
}
|
|
}
|
|
|
|
- (void)addRoomThirdPartyInviteToParticipants:(MXRoomThirdPartyInvite*)roomThirdPartyInvite roomState:(MXRoomState*)roomState
|
|
{
|
|
// If the homeserver has converted the 3pid invite into a room member, do no show it
|
|
// If the invite has been revoked (null display name), do not show it too.
|
|
if (![roomState memberWithThirdPartyInviteToken:roomThirdPartyInvite.token]
|
|
&& roomThirdPartyInvite.displayname)
|
|
{
|
|
Contact *contact = [[Contact alloc] initMatrixContactWithDisplayName:roomThirdPartyInvite.displayname andMatrixID:nil];
|
|
contact.isThirdPartyInvite = YES;
|
|
contact.mxThirdPartyInvite = roomThirdPartyInvite;
|
|
|
|
[invitedParticipants addObject:contact];
|
|
}
|
|
}
|
|
|
|
// key is a room member user id or a room 3pid invite token
|
|
- (void)removeParticipantByKey:(NSString*)key
|
|
{
|
|
NSUInteger index;
|
|
|
|
if (actualParticipants.count)
|
|
{
|
|
for (index = 0; index < actualParticipants.count; index++)
|
|
{
|
|
Contact *contact = actualParticipants[index];
|
|
|
|
if (contact.mxMember && [contact.mxMember.userId isEqualToString:key])
|
|
{
|
|
[actualParticipants removeObjectAtIndex:index];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (invitedParticipants.count)
|
|
{
|
|
for (index = 0; index < invitedParticipants.count; index++)
|
|
{
|
|
Contact *contact = invitedParticipants[index];
|
|
|
|
if (contact.mxMember && [contact.mxMember.userId isEqualToString:key])
|
|
{
|
|
[invitedParticipants removeObjectAtIndex:index];
|
|
return;
|
|
}
|
|
|
|
if (contact.mxThirdPartyInvite && [contact.mxThirdPartyInvite.token isEqualToString:key])
|
|
{
|
|
[invitedParticipants removeObjectAtIndex:index];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)finalizeParticipantsList:(MXRoomState*)roomState
|
|
{
|
|
// Sort contacts by last active, with "active now" first.
|
|
// ...and then by power
|
|
// ...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) {
|
|
|
|
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];
|
|
}
|
|
if (userA && !userB)
|
|
{
|
|
return NSOrderedAscending;
|
|
}
|
|
if (!userA && userB)
|
|
{
|
|
return NSOrderedDescending;
|
|
}
|
|
|
|
if (userA.currentlyActive && userB.currentlyActive)
|
|
{
|
|
// Order first by power levels (admins then moderators then others)
|
|
MXRoomPowerLevels *powerLevels = [roomState powerLevels];
|
|
NSInteger powerLevelA = [powerLevels powerLevelOfUserWithUserID:contactA.mxMember.userId];
|
|
NSInteger powerLevelB = [powerLevels powerLevelOfUserWithUserID:contactB.mxMember.userId];
|
|
|
|
if (powerLevelA == powerLevelB)
|
|
{
|
|
// Then order by name
|
|
if (contactA.sortingDisplayName.length && contactB.sortingDisplayName.length)
|
|
{
|
|
return [contactA.sortingDisplayName compare:contactB.sortingDisplayName options:NSCaseInsensitiveSearch];
|
|
}
|
|
else if (contactA.sortingDisplayName.length)
|
|
{
|
|
return NSOrderedAscending;
|
|
}
|
|
else if (contactB.sortingDisplayName.length)
|
|
{
|
|
return NSOrderedDescending;
|
|
}
|
|
return [contactA.displayName compare:contactB.displayName options:NSCaseInsensitiveSearch];
|
|
}
|
|
else
|
|
{
|
|
return powerLevelB - powerLevelA;
|
|
}
|
|
|
|
}
|
|
|
|
if (userA.currentlyActive && !userB.currentlyActive)
|
|
{
|
|
return NSOrderedAscending;
|
|
}
|
|
if (!userA.currentlyActive && userB.currentlyActive)
|
|
{
|
|
return NSOrderedDescending;
|
|
}
|
|
|
|
// Finally, compare the lastActiveAgo
|
|
NSUInteger lastActiveAgoA = userA.lastActiveAgo;
|
|
NSUInteger lastActiveAgoB = userB.lastActiveAgo;
|
|
|
|
if (lastActiveAgoA == lastActiveAgoB)
|
|
{
|
|
return NSOrderedSame;
|
|
}
|
|
else
|
|
{
|
|
return ((lastActiveAgoA > lastActiveAgoB) ? NSOrderedDescending : NSOrderedAscending);
|
|
}
|
|
};
|
|
|
|
// Sort each participants list in alphabetical order
|
|
[actualParticipants sortUsingComparator:comparator];
|
|
[invitedParticipants sortUsingComparator:comparator];
|
|
|
|
// Reload search result if any
|
|
[self reloadSearchResult];
|
|
}
|
|
|
|
- (void)addPendingActionMask
|
|
{
|
|
// Remove potential existing mask
|
|
[self removePendingActionMask];
|
|
|
|
// Add a spinner above the tableview to avoid that the user tap on any other button
|
|
pendingMaskSpinnerView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
|
|
pendingMaskSpinnerView.backgroundColor = [UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:0.5];
|
|
pendingMaskSpinnerView.frame = self.tableView.frame;
|
|
pendingMaskSpinnerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
|
|
|
|
// append it
|
|
[self.tableView.superview addSubview:pendingMaskSpinnerView];
|
|
|
|
// animate it
|
|
[pendingMaskSpinnerView startAnimating];
|
|
|
|
// Show the spinner after a delay so that if it is removed in a short future,
|
|
// it is not displayed to the end user.
|
|
pendingMaskSpinnerView.alpha = 0;
|
|
[UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
|
|
|
self->pendingMaskSpinnerView.alpha = 1;
|
|
|
|
} completion:^(BOOL finished) {
|
|
}];
|
|
}
|
|
|
|
- (void)removePendingActionMask
|
|
{
|
|
if (pendingMaskSpinnerView)
|
|
{
|
|
[pendingMaskSpinnerView removeFromSuperview];
|
|
pendingMaskSpinnerView = nil;
|
|
}
|
|
}
|
|
|
|
- (void)pushViewController:(UIViewController*)viewController
|
|
{
|
|
// Check whether the view controller is displayed inside a segmented one.
|
|
if (self.parentViewController.navigationController)
|
|
{
|
|
// Hide back button title
|
|
[self.parentViewController vc_removeBackTitle];
|
|
|
|
[self.parentViewController.navigationController pushViewController:viewController animated:YES];
|
|
}
|
|
else
|
|
{
|
|
// Hide back button title
|
|
[self vc_removeBackTitle];
|
|
|
|
[self.navigationController pushViewController:viewController animated:YES];
|
|
}
|
|
}
|
|
|
|
- (void)showDetailFor:(MXRoomMember* _Nonnull)member from:(UIView* _Nullable)sourceView {
|
|
memberDetailsViewController = [RoomMemberDetailsViewController roomMemberDetailsViewController];
|
|
|
|
// Set delegate to handle action on member (start chat, mention)
|
|
memberDetailsViewController.delegate = self;
|
|
memberDetailsViewController.enableMention = _enableMention;
|
|
memberDetailsViewController.enableVoipCall = NO;
|
|
|
|
[memberDetailsViewController displayRoomMember:member withMatrixRoom:self.mxRoom];
|
|
|
|
[self pushViewController:memberDetailsViewController];
|
|
}
|
|
|
|
#pragma mark - UITableView data source
|
|
|
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
|
{
|
|
NSInteger count = 0;
|
|
|
|
participantsSection = invitedSection = -1;
|
|
|
|
if (currentSearchText.length)
|
|
{
|
|
if (filteredActualParticipants.count)
|
|
{
|
|
participantsSection = count++;
|
|
}
|
|
|
|
if (filteredInvitedParticipants.count)
|
|
{
|
|
invitedSection = count++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (userParticipant || actualParticipants.count)
|
|
{
|
|
participantsSection = count++;
|
|
}
|
|
|
|
if (invitedParticipants.count)
|
|
{
|
|
invitedSection = count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
|
{
|
|
NSInteger count = 0;
|
|
|
|
if (section == participantsSection)
|
|
{
|
|
if (currentSearchText.length)
|
|
{
|
|
count = filteredActualParticipants.count;
|
|
}
|
|
else
|
|
{
|
|
count = actualParticipants.count;
|
|
if (userParticipant)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
else if (section == invitedSection)
|
|
{
|
|
if (currentSearchText.length)
|
|
{
|
|
count = filteredInvitedParticipants.count;
|
|
}
|
|
else
|
|
{
|
|
count = invitedParticipants.count;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
UITableViewCell *cell;
|
|
|
|
if (indexPath.section == participantsSection || indexPath.section == invitedSection)
|
|
{
|
|
ContactTableViewCell* participantCell = [tableView dequeueReusableCellWithIdentifier:@"ParticipantTableViewCellId" forIndexPath:indexPath];
|
|
participantCell.selectionStyle = UITableViewCellSelectionStyleNone;
|
|
participantCell.showCustomAccessoryView = self.showParticipantCustomAccessoryView;
|
|
|
|
participantCell.mxRoom = self.mxRoom;
|
|
|
|
Contact *contact;
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
if (contact)
|
|
{
|
|
[participantCell render:contact];
|
|
|
|
if (contact.mxMember)
|
|
{
|
|
MXRoomState *roomState = self.mxRoom.dangerousSyncState;
|
|
|
|
// 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];
|
|
}
|
|
}
|
|
}
|
|
|
|
cell = participantCell;
|
|
}
|
|
else
|
|
{
|
|
// Return a fake cell to prevent app from crashing.
|
|
cell = [[UITableViewCell alloc] init];
|
|
}
|
|
|
|
return cell;
|
|
}
|
|
|
|
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
if (indexPath.section == participantsSection || indexPath.section == invitedSection)
|
|
{
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath
|
|
{
|
|
// iOS8 requires this method to enable editing (see editActionsForRowAtIndexPath).
|
|
}
|
|
|
|
#pragma mark - UITableView delegate
|
|
|
|
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
|
|
{
|
|
cell.backgroundColor = ThemeService.shared.theme.backgroundColor;
|
|
|
|
// Update the selected background view
|
|
if (ThemeService.shared.theme.selectedBackgroundColor)
|
|
{
|
|
cell.selectedBackgroundView = [[UIView alloc] init];
|
|
cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor;
|
|
}
|
|
else
|
|
{
|
|
if (tableView.style == UITableViewStylePlain)
|
|
{
|
|
cell.selectedBackgroundView = nil;
|
|
}
|
|
else
|
|
{
|
|
cell.selectedBackgroundView.backgroundColor = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
|
{
|
|
CGFloat height = 0.0;
|
|
|
|
if (section == invitedSection)
|
|
{
|
|
height = 30.0;
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
|
{
|
|
UIView* sectionHeader;
|
|
|
|
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];
|
|
}
|
|
|
|
return sectionHeader;
|
|
}
|
|
|
|
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
return 74.0;
|
|
}
|
|
|
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
// Sanity check
|
|
if (!self.mxRoom)
|
|
{
|
|
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];
|
|
}
|
|
}
|
|
|
|
if (contact.mxMember)
|
|
{
|
|
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
|
|
[self showDetailFor:contact.mxMember from:selectedCell];
|
|
}
|
|
|
|
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
|
}
|
|
|
|
- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
NSMutableArray* actions;
|
|
|
|
// add the swipe to delete only on participants sections
|
|
if (indexPath.section == participantsSection || indexPath.section == invitedSection)
|
|
{
|
|
actions = [[NSMutableArray alloc] init];
|
|
|
|
// Patch: Force the width of the button by adding whitespace characters into the title string.
|
|
UITableViewRowAction *leaveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@" " handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){
|
|
|
|
[self onDeleteAt:indexPath];
|
|
|
|
}];
|
|
|
|
leaveAction.backgroundColor = [MXKTools convertImageToPatternColor:@"remove_icon" backgroundColor:ThemeService.shared.theme.headerBackgroundColor patternSize:CGSizeMake(74, 74) resourceSize:CGSizeMake(24, 24)];
|
|
[actions insertObject:leaveAction atIndex:0];
|
|
}
|
|
|
|
return actions;
|
|
}
|
|
|
|
#pragma mark - MXKRoomMemberDetailsViewControllerDelegate
|
|
|
|
- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString *)matrixId completion:(void (^)(void))completion
|
|
{
|
|
[[AppDelegate theDelegate] showNewDirectChat:matrixId withMatrixSession:self.mxRoom.mxSession completion:completion];
|
|
}
|
|
|
|
- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController mention:(MXRoomMember*)member
|
|
{
|
|
if (_delegate)
|
|
{
|
|
id<RoomParticipantsViewControllerDelegate> delegate = _delegate;
|
|
|
|
// Withdraw the current view controller, and let the delegate mention the member
|
|
[self withdrawViewControllerAnimated:YES completion:^{
|
|
|
|
[delegate roomParticipantsViewController:self mention:member];
|
|
|
|
}];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Actions
|
|
|
|
- (void)onDeleteAt:(NSIndexPath*)path
|
|
{
|
|
NSUInteger section = path.section;
|
|
NSUInteger row = path.row;
|
|
|
|
if (section == participantsSection || section == invitedSection)
|
|
{
|
|
if (currentAlert)
|
|
{
|
|
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
|
currentAlert = nil;
|
|
}
|
|
|
|
if (section == participantsSection && userParticipant && (0 == row) && !currentSearchText.length)
|
|
{
|
|
// Leave ?
|
|
MXWeakify(self);
|
|
|
|
NSString *title, *message;
|
|
if (self.mxRoom.isDirect)
|
|
{
|
|
title = [VectorL10n roomParticipantsLeavePromptTitleForDm];
|
|
message = [VectorL10n roomParticipantsLeavePromptMsgForDm];
|
|
}
|
|
else
|
|
{
|
|
title = [VectorL10n roomParticipantsLeavePromptTitle];
|
|
message = [VectorL10n roomParticipantsLeavePromptMsg];
|
|
}
|
|
|
|
currentAlert = [UIAlertController alertControllerWithTitle:title
|
|
message:message
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
|
|
style:UIAlertActionStyleCancel
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
self->currentAlert = nil;
|
|
|
|
}]];
|
|
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n leave]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
self->currentAlert = nil;
|
|
|
|
[self addPendingActionMask];
|
|
MXWeakify(self);
|
|
[self.mxRoom leave:^{
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self withdrawViewControllerAnimated:YES completion:nil];
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self removePendingActionMask];
|
|
MXLogDebug(@"[RoomParticipantsVC] Leave room %@ failed", self.mxRoom.roomId);
|
|
// Alert user
|
|
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
|
|
|
}];
|
|
|
|
}]];
|
|
|
|
[currentAlert mxk_setAccessibilityIdentifier:@"RoomParticipantsVCLeaveAlert"];
|
|
[self presentViewController:currentAlert animated:YES completion:nil];
|
|
}
|
|
else
|
|
{
|
|
NSMutableArray *participants;
|
|
|
|
if (section == participantsSection)
|
|
{
|
|
if (currentSearchText.length)
|
|
{
|
|
participants = filteredActualParticipants;
|
|
}
|
|
else
|
|
{
|
|
participants = actualParticipants;
|
|
|
|
if (userParticipant)
|
|
{
|
|
row --;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (currentSearchText.length)
|
|
{
|
|
participants = filteredInvitedParticipants;
|
|
}
|
|
else
|
|
{
|
|
participants = invitedParticipants;
|
|
}
|
|
}
|
|
|
|
if (row < participants.count)
|
|
{
|
|
Contact *contact = participants[row];
|
|
MXWeakify(self);
|
|
|
|
if (contact.mxMember)
|
|
{
|
|
NSString *memberUserId = contact.mxMember.userId;
|
|
|
|
// Kick ?
|
|
NSString *promptMsg = [VectorL10n roomParticipantsRemovePromptMsg:(contact ? contact.displayName : memberUserId)];
|
|
currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomParticipantsRemovePromptTitle]
|
|
message:promptMsg
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
|
|
style:UIAlertActionStyleCancel
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
self->currentAlert = nil;
|
|
|
|
}]];
|
|
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n remove]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
self->currentAlert = nil;
|
|
|
|
[self addPendingActionMask];
|
|
MXWeakify(self);
|
|
[self.mxRoom kickUser:memberUserId
|
|
reason:nil
|
|
success:^{
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self removePendingActionMask];
|
|
|
|
[participants removeObjectAtIndex:row];
|
|
|
|
// Refresh display
|
|
[self.tableView reloadData];
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self removePendingActionMask];
|
|
MXLogDebug(@"[RoomParticipantsVC] Kick %@ failed", memberUserId);
|
|
// Alert user
|
|
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
|
|
|
}];
|
|
|
|
}]];
|
|
}
|
|
else if (contact.mxThirdPartyInvite)
|
|
{
|
|
// This is a third-party invite
|
|
currentAlert = [UIAlertController alertControllerWithTitle:nil
|
|
message:[VectorL10n roomParticipantsRemoveThirdPartyInvitePromptMsg]
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
|
|
style:UIAlertActionStyleCancel
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
self->currentAlert = nil;
|
|
|
|
}]];
|
|
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n remove]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
self->currentAlert = nil;
|
|
|
|
[self addPendingActionMask];
|
|
MXWeakify(self);
|
|
[self.mxRoom sendStateEventOfType:kMXEventTypeStringRoomThirdPartyInvite
|
|
content:@{} stateKey:contact.mxThirdPartyInvite.token success:^(NSString *eventId) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self removePendingActionMask];
|
|
|
|
[participants removeObjectAtIndex:row];
|
|
|
|
// Refresh display
|
|
[self.tableView reloadData];
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self removePendingActionMask];
|
|
MXLogDebug(@"[RoomParticipantsVC] Revoke 3pid invite failed");
|
|
// Alert user
|
|
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
|
|
|
}];
|
|
|
|
}]];
|
|
}
|
|
|
|
[currentAlert mxk_setAccessibilityIdentifier:@"RoomParticipantsVCKickAlert"];
|
|
[self presentViewController:currentAlert animated:YES completion:nil];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)onCancel:(id)sender
|
|
{
|
|
[self withdrawViewControllerAnimated:YES completion:nil];
|
|
}
|
|
|
|
#pragma mark - UISearchBar delegate
|
|
|
|
- (void)refreshSearchBarItemsColor:(UISearchBar *)searchBar
|
|
{
|
|
// bar tint color
|
|
searchBar.barTintColor = searchBar.tintColor = ThemeService.shared.theme.tintColor;
|
|
searchBar.tintColor = ThemeService.shared.theme.tintColor;
|
|
|
|
// FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals.
|
|
|
|
// text color
|
|
UITextField *searchBarTextField = searchBar.vc_searchTextField;
|
|
searchBarTextField.textColor = ThemeService.shared.theme.textSecondaryColor;
|
|
|
|
// Magnifying glass icon.
|
|
UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView;
|
|
leftImageView.image = [leftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
|
|
|
// remove the gray background color
|
|
UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"];
|
|
UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"];
|
|
effectBackgroundTop.hidden = YES;
|
|
effectBackgroundBottom.hidden = YES;
|
|
|
|
// place holder
|
|
searchBarTextField.textColor = ThemeService.shared.theme.searchPlaceholderColor;
|
|
}
|
|
|
|
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
|
{
|
|
// Update search results.
|
|
NSUInteger index;
|
|
MXKContact *contact;
|
|
|
|
searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
|
|
if (!currentSearchText.length || [searchText hasPrefix:currentSearchText] == NO)
|
|
{
|
|
// Copy participants and invited participants
|
|
filteredActualParticipants = [NSMutableArray arrayWithArray:actualParticipants];
|
|
filteredInvitedParticipants = [NSMutableArray arrayWithArray:invitedParticipants];
|
|
|
|
// Add the current user if he belongs to the room members.
|
|
if (userParticipant)
|
|
{
|
|
[filteredActualParticipants addObject:userParticipant];
|
|
}
|
|
}
|
|
|
|
currentSearchText = searchText;
|
|
|
|
// Filter room participants
|
|
if (currentSearchText.length)
|
|
{
|
|
for (index = 0; index < filteredActualParticipants.count;)
|
|
{
|
|
contact = filteredActualParticipants[index];
|
|
if (![contact matchedWithPatterns:@[currentSearchText]])
|
|
{
|
|
[filteredActualParticipants removeObjectAtIndex:index];
|
|
}
|
|
else
|
|
{
|
|
index++;
|
|
}
|
|
}
|
|
|
|
for (index = 0; index < filteredInvitedParticipants.count;)
|
|
{
|
|
contact = filteredInvitedParticipants[index];
|
|
if (![contact matchedWithPatterns:@[currentSearchText]])
|
|
{
|
|
[filteredInvitedParticipants removeObjectAtIndex:index];
|
|
}
|
|
else
|
|
{
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
filteredActualParticipants = nil;
|
|
filteredInvitedParticipants = nil;
|
|
}
|
|
|
|
// Refresh display
|
|
[self refreshTableView];
|
|
}
|
|
|
|
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
|
|
{
|
|
searchBar.showsCancelButton = YES;
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
|
|
{
|
|
searchBar.showsCancelButton = NO;
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
|
|
{
|
|
// "Done" key has been pressed.
|
|
|
|
// Dismiss keyboard
|
|
[_searchBarView resignFirstResponder];
|
|
}
|
|
|
|
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
|
|
{
|
|
if (currentSearchText)
|
|
{
|
|
currentSearchText = nil;
|
|
filteredActualParticipants = nil;
|
|
filteredInvitedParticipants = nil;
|
|
|
|
[self refreshTableView];
|
|
}
|
|
|
|
searchBar.text = nil;
|
|
// Leave search
|
|
[searchBar resignFirstResponder];
|
|
}
|
|
|
|
#pragma mark - RoomParticipantsInviteCoordinatorBridgePresenterDelegate
|
|
|
|
- (void)roomParticipantsInviteCoordinatorBridgePresenterDidComplete:(RoomParticipantsInviteCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
|
{
|
|
self->invitePresenter = nil;
|
|
}
|
|
|
|
- (void)roomParticipantsInviteCoordinatorBridgePresenterDidStartLoading:(RoomParticipantsInviteCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
|
{
|
|
[self addPendingActionMask];
|
|
}
|
|
|
|
- (void)roomParticipantsInviteCoordinatorBridgePresenterDidEndLoading:(RoomParticipantsInviteCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
|
{
|
|
[self removePendingActionMask];
|
|
}
|
|
|
|
@end
|