/* Copyright 2018-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 "MXKRoomMemberDetailsViewController.h" @import MatrixSDK.MXMediaManager; #import "MXKTableViewCellWithButtons.h" #import "NSBundle+MatrixKit.h" #import "MXKAppSettings.h" #import "MXKConstants.h" #import "MXKSwiftHeader.h" @interface MXKRoomMemberDetailsViewController () { id membersListener; // mask view while processing a request UIActivityIndicatorView * pendingMaskSpinnerView; // Observe left rooms id leaveRoomNotificationObserver; // Observe kMXRoomDidFlushDataNotification to take into account the updated room members when the room history is flushed. id roomDidFlushDataNotificationObserver; // Cache for the room live timeline id mxRoomLiveTimeline; } @end @implementation MXKRoomMemberDetailsViewController @synthesize mxRoom; + (UINib *)nib { return [UINib nibWithNibName:NSStringFromClass([MXKRoomMemberDetailsViewController class]) bundle:[NSBundle bundleForClass:[MXKRoomMemberDetailsViewController class]]]; } + (instancetype)roomMemberDetailsViewController { return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKRoomMemberDetailsViewController class]) bundle:[NSBundle bundleForClass:[MXKRoomMemberDetailsViewController class]]]; } - (void)finalizeInit { [super finalizeInit]; actionsArray = [[NSMutableArray alloc] init]; _enableLeave = YES; } - (void)viewDidLoad { [super viewDidLoad]; // Check whether the view controller has been pushed via storyboard if (!self.tableView) { // Instantiate view controller objects [[[self class] nib] instantiateWithOwner:self options:nil]; } // ignore useless update if (_mxRoomMember) { [self initObservers]; } } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self initObservers]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self removeObservers]; } - (void)destroy { // close any pending actionsheet if (currentAlert) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; currentAlert = nil; } [self removePendingActionMask]; [self removeObservers]; _delegate = nil; _mxRoomMember = nil; actionsArray = nil; [super destroy]; } #pragma mark - - (void)displayRoomMember:(MXRoomMember*)roomMember withMatrixRoom:(MXRoom*)room { [self removeObservers]; mxRoom = room; MXWeakify(self); [mxRoom liveTimeline:^(id liveTimeline) { MXStrongifyAndReturnIfNil(self); self->mxRoomLiveTimeline = liveTimeline; // Update matrix session associated to the view controller NSArray *mxSessions = self.mxSessions; for (MXSession *mxSession in mxSessions) { [self removeMatrixSession:mxSession]; } [self addMatrixSession:room.mxSession]; self->_mxRoomMember = roomMember; [self initObservers]; }]; } - (id )mxRoomLiveTimeline { // @TODO(async-state): Just here for dev NSAssert(mxRoomLiveTimeline, @"[MXKRoomMemberDetailsViewController] Room live timeline must be preloaded before accessing to MXKRoomMemberDetailsViewController.mxRoomLiveTimeline"); return mxRoomLiveTimeline; } - (UIImage*)picturePlaceholder { return [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"default-profile"]; } - (void)setEnableMention:(BOOL)enableMention { if (_enableMention != enableMention) { _enableMention = enableMention; [self updateMemberInfo]; } } - (void)setEnableVoipCall:(BOOL)enableVoipCall { if (_enableVoipCall != enableVoipCall) { _enableVoipCall = enableVoipCall; [self updateMemberInfo]; } } - (void)setEnableLeave:(BOOL)enableLeave { if (_enableLeave != enableLeave) { _enableLeave = enableLeave; [self updateMemberInfo]; } } - (IBAction)onActionButtonPressed:(id)sender { if ([sender isKindOfClass:[UIButton class]]) { // Check whether an action is already in progress if ([self hasPendingAction]) { return; } UIButton *button = (UIButton*)sender; switch (button.tag) { case MXKRoomMemberDetailsActionInvite: { [self addPendingActionMask]; [mxRoom inviteUser:_mxRoomMember.userId success:^{ [self removePendingActionMask]; } failure:^(NSError *error) { [self removePendingActionMask]; MXLogDebug(@"[MXKRoomMemberDetailsVC] Invite %@ failed", self->_mxRoomMember.userId); // Notify MatrixKit user NSString *myUserId = self.mainSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; break; } case MXKRoomMemberDetailsActionLeave: { MXWeakify(self); [self.mxRoom isLastOwnerWithCompletionHandler:^(BOOL isLastOwner, NSError* error){ if (isLastOwner) { UIAlertController *isLastOwnerPrompt = [UIAlertController alertControllerWithTitle:[VectorL10n error] message:[VectorL10n roomParticipantsLeaveNotAllowedForLastOwnerMsg] preferredStyle:UIAlertControllerStyleAlert]; [isLastOwnerPrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; }]]; MXStrongifyAndReturnIfNil(self); dispatch_async(dispatch_get_main_queue(), ^{ [self presentViewController:isLastOwnerPrompt animated:YES completion:nil]; self->currentAlert = isLastOwnerPrompt; }); } else { MXStrongifyAndReturnIfNil(self); [self addPendingActionMask]; MXWeakify(self); [self.mxRoom leave:^{ MXStrongifyAndReturnIfNil(self); [self removePendingActionMask]; [self withdrawViewControllerAnimated:YES completion:nil]; } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); [self removePendingActionMask]; MXLogDebug(@"[MXKRoomMemberDetailsVC] Leave room %@ failed", self->mxRoom.roomId); // Notify MatrixKit user NSString *myUserId = self.mainSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; } }]; break; } case MXKRoomMemberDetailsActionKick: { [self addPendingActionMask]; [mxRoom kickUser:_mxRoomMember.userId reason:nil success:^{ [self removePendingActionMask]; // Pop/Dismiss the current view controller if the left members are hidden if (![[MXKAppSettings standardAppSettings] showLeftMembersInRoomMemberList]) { [self withdrawViewControllerAnimated:YES completion:nil]; } } failure:^(NSError *error) { [self removePendingActionMask]; MXLogDebug(@"[MXKRoomMemberDetailsVC] Kick %@ failed", self->_mxRoomMember.userId); // Notify MatrixKit user NSString *myUserId = self.mainSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; break; } case MXKRoomMemberDetailsActionBan: { [self addPendingActionMask]; [mxRoom banUser:_mxRoomMember.userId reason:nil success:^{ [self removePendingActionMask]; } failure:^(NSError *error) { [self removePendingActionMask]; MXLogDebug(@"[MXKRoomMemberDetailsVC] Ban %@ failed", self->_mxRoomMember.userId); // Notify MatrixKit user NSString *myUserId = self.mainSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; break; } case MXKRoomMemberDetailsActionUnban: { [self addPendingActionMask]; [mxRoom unbanUser:_mxRoomMember.userId success:^{ [self removePendingActionMask]; } failure:^(NSError *error) { [self removePendingActionMask]; MXLogDebug(@"[MXKRoomMemberDetailsVC] Unban %@ failed", self->_mxRoomMember.userId); // Notify MatrixKit user NSString *myUserId = self.mainSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; break; } case MXKRoomMemberDetailsActionIgnore: { // Prompt user to ignore content from this user MXWeakify(self); if (currentAlert) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; } currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomMemberIgnorePrompt] message:nil preferredStyle:UIAlertControllerStyleAlert]; [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n yes] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; // Add the user to the blacklist: ignored users [self addPendingActionMask]; MXWeakify(self); [self.mainSession ignoreUsers:@[self.mxRoomMember.userId] success:^{ MXStrongifyAndReturnIfNil(self); [self removePendingActionMask]; } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); [self removePendingActionMask]; MXLogDebug(@"[MXKRoomMemberDetailsVC] Ignore %@ failed", self.mxRoomMember.userId); // Notify MatrixKit user NSString *myUserId = self.mainSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; }]]; [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n no] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; }]]; [self presentViewController:currentAlert animated:YES completion:nil]; break; } case MXKRoomMemberDetailsActionUnignore: { // Remove the member from the ignored user list. [self addPendingActionMask]; MXWeakify(self); [self.mainSession unIgnoreUsers:@[self.mxRoomMember.userId] success:^{ MXStrongifyAndReturnIfNil(self); [self removePendingActionMask]; } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); [self removePendingActionMask]; MXLogDebug(@"[MXKRoomMemberDetailsVC] Unignore %@ failed", self.mxRoomMember.userId); // Notify MatrixKit user NSString *myUserId = self.mainSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; break; } case MXKRoomMemberDetailsActionSetDefaultPowerLevel: { break; } case MXKRoomMemberDetailsActionSetModerator: { break; } case MXKRoomMemberDetailsActionSetAdmin: { break; } case MXKRoomMemberDetailsActionSetCustomPowerLevel: { [self updateUserPowerLevel]; break; } case MXKRoomMemberDetailsActionStartChat: { if (self.delegate) { [self addPendingActionMask]; [self.delegate roomMemberDetailsViewController:self startChatWithMemberId:_mxRoomMember.userId completion:^{ [self removePendingActionMask]; }]; } break; } case MXKRoomMemberDetailsActionStartVoiceCall: case MXKRoomMemberDetailsActionStartVideoCall: { BOOL isVideoCall = (button.tag == MXKRoomMemberDetailsActionStartVideoCall); if (self.delegate && [self.delegate respondsToSelector:@selector(roomMemberDetailsViewController:placeVoipCallWithMemberId:andVideo:)]) { [self addPendingActionMask]; [self.delegate roomMemberDetailsViewController:self placeVoipCallWithMemberId:_mxRoomMember.userId andVideo:isVideoCall]; [self removePendingActionMask]; } else { [self addPendingActionMask]; MXRoom* directRoom = [self.mainSession directJoinedRoomWithUserId:_mxRoomMember.userId]; // Place the call directly if the room exists if (directRoom) { [directRoom placeCallWithVideo:isVideoCall success:nil failure:nil]; [self removePendingActionMask]; } else { // Create a new room MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters parametersForDirectRoomWithUser:_mxRoomMember.userId]; [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { // Delay the call in order to be sure that the room is ready dispatch_async(dispatch_get_main_queue(), ^{ [room placeCallWithVideo:isVideoCall success:nil failure:nil]; [self removePendingActionMask]; }); } failure:^(NSError *error) { MXLogDebug(@"[MXKRoomMemberDetailsVC] Create room failed"); [self removePendingActionMask]; // Notify MatrixKit user NSString *myUserId = self.mainSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; } } break; } case MXKRoomMemberDetailsActionMention: { // Sanity check if (_delegate && [_delegate respondsToSelector:@selector(roomMemberDetailsViewController:mention:)]) { id delegate = _delegate; MXRoomMember *member = _mxRoomMember; // Withdraw the current view controller, and let the delegate mention the member [self withdrawViewControllerAnimated:YES completion:^{ [delegate roomMemberDetailsViewController:self mention:member]; }]; } break; } default: break; } } } #pragma mark - Internals - (void)initObservers { // Remove any pending observers [self removeObservers]; if (mxRoom) { // Observe room's members update NSArray *mxMembersEvents = @[kMXEventTypeStringRoomMember, kMXEventTypeStringRoomPowerLevels]; self->membersListener = [mxRoom listenToEventsOfTypes:mxMembersEvents onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { // consider only live event if (direction == MXTimelineDirectionForwards) { [self refreshRoomMember]; } }]; // Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room. 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 member if (notif.object == self.mainSession) { NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey]; if (roomId && [roomId isEqualToString:self->mxRoom.roomId]) { // We must remove the current view controller. [self withdrawViewControllerAnimated:YES completion:nil]; } } }]; // Observe room history flush (sync with limited timeline, or state event redaction) roomDidFlushDataNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomDidFlushDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { MXRoom *room = notif.object; if (self.mainSession == 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 by updating the room member instance [self refreshRoomMember]; } }]; } [self updateMemberInfo]; } - (void)removeObservers { if (leaveRoomNotificationObserver) { [[NSNotificationCenter defaultCenter] removeObserver:leaveRoomNotificationObserver]; leaveRoomNotificationObserver = nil; } if (roomDidFlushDataNotificationObserver) { [[NSNotificationCenter defaultCenter] removeObserver:roomDidFlushDataNotificationObserver]; roomDidFlushDataNotificationObserver = nil; } [[NSNotificationCenter defaultCenter] removeObserver:self]; if (membersListener && mxRoom) { MXWeakify(self); [mxRoom liveTimeline:^(id liveTimeline) { MXStrongifyAndReturnIfNil(self); [liveTimeline removeListener:self->membersListener]; self->membersListener = nil; }]; } } - (void)refreshRoomMember { // Hide potential action sheet if (currentAlert) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; currentAlert = nil; } MXRoomMember* nextRoomMember = nil; // get the updated memmber NSArray *membersList = self.mxRoomLiveTimeline.state.members.members; for (MXRoomMember* member in membersList) { if ([member.userId isEqualToString:_mxRoomMember.userId]) { nextRoomMember = member; break; } } // does the member still exist ? if (nextRoomMember) { // Refresh member _mxRoomMember = nextRoomMember; [self updateMemberInfo]; } else { [self withdrawViewControllerAnimated:YES completion:nil]; } } - (void)updateMemberInfo { self.title = _mxRoomMember.displayname ? _mxRoomMember.displayname : _mxRoomMember.userId; // set the thumbnail info self.memberThumbnail.contentMode = UIViewContentModeScaleAspectFill; self.memberThumbnail.defaultBackgroundColor = [UIColor clearColor]; [self.memberThumbnail.layer setCornerRadius:self.memberThumbnail.frame.size.width / 2]; [self.memberThumbnail setClipsToBounds:YES]; self.memberThumbnail.mediaFolder = kMXMediaManagerAvatarThumbnailFolder; self.memberThumbnail.enableInMemoryCache = YES; [self.memberThumbnail setImageURI:_mxRoomMember.avatarUrl withType:nil andImageOrientation:UIImageOrientationUp toFitViewSize:self.memberThumbnail.frame.size withMethod:MXThumbnailingMethodCrop previewImage:self.picturePlaceholder mediaManager:self.mainSession.mediaManager]; self.roomMemberMatrixInfo.text = _mxRoomMember.userId; [self.tableView reloadData]; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Check user's power level before allowing an action (kick, ban, ...) MXRoomState *roomState = self.mxRoomLiveTimeline.state; MXRoomPowerLevels *powerLevels = [self.mxRoomLiveTimeline.state powerLevels]; NSInteger memberPowerLevel = [roomState powerLevelOfUserWithUserID:_mxRoomMember.userId]; NSInteger oneSelfPowerLevel = [roomState powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; [actionsArray removeAllObjects]; // Consider the case of the user himself if ([_mxRoomMember.userId isEqualToString:self.mainSession.myUser.userId]) { if (_enableLeave) { [actionsArray addObject:@(MXKRoomMemberDetailsActionLeave)]; } if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels]) { [actionsArray addObject:@(MXKRoomMemberDetailsActionSetCustomPowerLevel)]; } } else if (_mxRoomMember) { if (_enableVoipCall) { // Offer voip call options [actionsArray addObject:@(MXKRoomMemberDetailsActionStartVoiceCall)]; [actionsArray addObject:@(MXKRoomMemberDetailsActionStartVideoCall)]; } // Consider membership of the selected member switch (_mxRoomMember.membership) { case MXMembershipInvite: case MXMembershipJoin: { // Check conditions to be able to kick someone if (oneSelfPowerLevel >= [powerLevels kick] && oneSelfPowerLevel > memberPowerLevel) { [actionsArray addObject:@(MXKRoomMemberDetailsActionKick)]; } // Check conditions to be able to ban someone if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel > memberPowerLevel) { [actionsArray addObject:@(MXKRoomMemberDetailsActionBan)]; } // Check whether the option Ignore may be presented if (_mxRoomMember.membership == MXMembershipJoin) { // is he already ignored ? if (![self.mainSession isUserIgnored:_mxRoomMember.userId]) { [actionsArray addObject:@(MXKRoomMemberDetailsActionIgnore)]; } else { [actionsArray addObject:@(MXKRoomMemberDetailsActionUnignore)]; } } break; } case MXMembershipLeave: { // Check conditions to be able to invite someone if (oneSelfPowerLevel >= [powerLevels invite]) { [actionsArray addObject:@(MXKRoomMemberDetailsActionInvite)]; } // Check conditions to be able to ban someone if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel > memberPowerLevel) { [actionsArray addObject:@(MXKRoomMemberDetailsActionBan)]; } break; } case MXMembershipBan: { // Check conditions to be able to unban someone if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel > memberPowerLevel) { [actionsArray addObject:@(MXKRoomMemberDetailsActionUnban)]; } break; } default: { break; } } // update power level if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels] && oneSelfPowerLevel > memberPowerLevel) { [actionsArray addObject:@(MXKRoomMemberDetailsActionSetCustomPowerLevel)]; } // offer to start a new chat only if the room is not the first direct chat with this user // it does not make sense : it would open the same room MXRoom* directRoom = [self.mainSession directJoinedRoomWithUserId:_mxRoomMember.userId]; if (!directRoom || (![directRoom.roomId isEqualToString:mxRoom.roomId])) { [actionsArray addObject:@(MXKRoomMemberDetailsActionStartChat)]; } } if (_enableMention) { // Add mention option [actionsArray addObject:@(MXKRoomMemberDetailsActionMention)]; } return (actionsArray.count + 1) / 2; } - (NSString*)actionButtonTitle:(MXKRoomMemberDetailsAction)action { NSString *title; switch (action) { case MXKRoomMemberDetailsActionInvite: title = [VectorL10n invite]; break; case MXKRoomMemberDetailsActionLeave: title = [VectorL10n leave]; break; case MXKRoomMemberDetailsActionKick: title = [VectorL10n kick]; break; case MXKRoomMemberDetailsActionBan: title = [VectorL10n ban]; break; case MXKRoomMemberDetailsActionUnban: title = [VectorL10n unban]; break; case MXKRoomMemberDetailsActionIgnore: title = [VectorL10n ignore]; break; case MXKRoomMemberDetailsActionUnignore: title = [VectorL10n unignore]; break; case MXKRoomMemberDetailsActionSetDefaultPowerLevel: title = [VectorL10n setDefaultPowerLevel]; break; case MXKRoomMemberDetailsActionSetModerator: title = [VectorL10n setModerator]; break; case MXKRoomMemberDetailsActionSetAdmin: title = [VectorL10n setAdmin]; break; case MXKRoomMemberDetailsActionSetCustomPowerLevel: title = [VectorL10n setPowerLevel]; break; case MXKRoomMemberDetailsActionStartChat: title = [VectorL10n startChat]; break; case MXKRoomMemberDetailsActionStartVoiceCall: title = [VectorL10n startVoiceCall]; break; case MXKRoomMemberDetailsActionStartVideoCall: title = [VectorL10n startVideoCall]; break; case MXKRoomMemberDetailsActionMention: title = [VectorL10n mention]; break; default: break; } return title; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (self.tableView == tableView) { NSInteger row = indexPath.row; MXKTableViewCellWithButtons *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButtons defaultReuseIdentifier]]; if (!cell) { cell = [[MXKTableViewCellWithButtons alloc] init]; } cell.mxkButtonNumber = 2; NSArray *buttons = cell.mxkButtons; NSInteger index = row * 2; NSString *text = nil; for (UIButton *button in buttons) { NSNumber *actionNumber; if (index < actionsArray.count) { actionNumber = [actionsArray objectAtIndex:index]; } text = (actionNumber ? [self actionButtonTitle:actionNumber.unsignedIntegerValue] : nil); button.hidden = (text.length == 0); button.layer.borderColor = button.tintColor.CGColor; button.layer.borderWidth = 1; button.layer.cornerRadius = 5; [button setTitle:text forState:UIControlStateNormal]; [button setTitle:text forState:UIControlStateHighlighted]; [button addTarget:self action:@selector(onActionButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; button.tag = (actionNumber ? actionNumber.unsignedIntegerValue : -1); index ++; } return cell; } // Return a fake cell to prevent app from crashing. return [[UITableViewCell alloc] init]; } #pragma mark - button management - (BOOL)hasPendingAction { return nil != pendingMaskSpinnerView; } - (void)addPendingActionMask { // 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.5 green:0.5 blue:0.5 alpha:0.5]; pendingMaskSpinnerView.frame = self.tableView.frame; pendingMaskSpinnerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin; // append it [self.tableView.superview addSubview:pendingMaskSpinnerView]; // animate it [pendingMaskSpinnerView startAnimating]; } - (void)removePendingActionMask { if (pendingMaskSpinnerView) { [pendingMaskSpinnerView removeFromSuperview]; pendingMaskSpinnerView = nil; [self.tableView reloadData]; } } - (void)setPowerLevel:(NSInteger)value promptUser:(BOOL)promptUser { NSInteger currentPowerLevel = [self.mxRoomLiveTimeline.state powerLevelOfUserWithUserID:_mxRoomMember.userId]; // check if the power level has not yet been set to 0 if (value != currentPowerLevel) { __weak typeof(self) weakSelf = self; if (promptUser && value == [self.mxRoomLiveTimeline.state powerLevelOfUserWithUserID:self.mainSession.myUser.userId]) { // If the user is setting the same power level as his to another user, ask him for a confirmation if (currentAlert) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; } currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomMemberPowerLevelPrompt] message:nil preferredStyle:UIAlertControllerStyleAlert]; [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n no] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; } }]]; [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n yes] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; // The user confirms. Apply the power level [self setPowerLevel:value promptUser:NO]; } }]]; [self presentViewController:currentAlert animated:YES completion:nil]; } else { [self addPendingActionMask]; // Reset user power level [self.mxRoom setPowerLevelOfUserWithUserID:_mxRoomMember.userId powerLevel:value success:^{ __strong __typeof(weakSelf)strongSelf = weakSelf; [strongSelf removePendingActionMask]; } failure:^(NSError *error) { __strong __typeof(weakSelf)strongSelf = weakSelf; [strongSelf removePendingActionMask]; MXLogDebug(@"[MXKRoomMemberDetailsVC] Set user power (%@) failed", strongSelf.mxRoomMember.userId); // Notify MatrixKit user NSString *myUserId = strongSelf.mainSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; } } } - (void)updateUserPowerLevel { __weak typeof(self) weakSelf = self; if (currentAlert) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; } currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n powerLevel] message:nil preferredStyle:UIAlertControllerStyleAlert]; if (![self.mainSession.myUser.userId isEqualToString:_mxRoomMember.userId]) { [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n resetToDefault] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; [self setPowerLevel:self.mxRoomLiveTimeline.state.powerLevels.usersDefault promptUser:YES]; } }]]; } [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { typeof(self) self = weakSelf; textField.secureTextEntry = NO; textField.text = [NSString stringWithFormat:@"%ld", (long)[self.mxRoomLiveTimeline.state powerLevelOfUserWithUserID:self.mxRoomMember.userId]]; textField.placeholder = nil; textField.keyboardType = UIKeyboardTypeDecimalPad; }]; [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; NSString *text = [self->currentAlert textFields].firstObject.text; self->currentAlert = nil; if (text.length > 0) { [self setPowerLevel:[text integerValue] promptUser:YES]; } } }]]; [self presentViewController:currentAlert animated:YES completion:nil]; } @end