mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-30 08:12:40 +00:00
962 lines
38 KiB
Objective-C
962 lines
38 KiB
Objective-C
/*
|
|
Copyright 2016 OpenMarket Ltd
|
|
Copyright 2017 Vector Creations Ltd
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
#import "RoomMemberDetailsViewController.h"
|
|
|
|
#import "AppDelegate.h"
|
|
|
|
#import "RoomMemberTitleView.h"
|
|
|
|
#import "AvatarGenerator.h"
|
|
#import "Tools.h"
|
|
|
|
#import "TableViewCellWithButton.h"
|
|
#import "RoomTableViewCell.h"
|
|
|
|
#import "EncryptionInfoView.h"
|
|
|
|
#define TABLEVIEW_ROW_CELL_HEIGHT 46
|
|
#define TABLEVIEW_SECTION_HEADER_HEIGHT 28
|
|
#define TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN 0.01f
|
|
|
|
@interface RoomMemberDetailsViewController ()
|
|
{
|
|
RoomMemberTitleView* memberTitleView;
|
|
|
|
/**
|
|
List of the admin actions on this member.
|
|
*/
|
|
NSMutableArray<NSNumber*> *adminActionsArray;
|
|
NSInteger adminToolsIndex;
|
|
|
|
/**
|
|
List of the basic actions on this member.
|
|
*/
|
|
NSMutableArray<NSNumber*> *otherActionsArray;
|
|
NSInteger otherActionsIndex;
|
|
|
|
/**
|
|
List of the direct chats (room ids) with this member.
|
|
*/
|
|
NSMutableArray<NSString*> *directChatsArray;
|
|
NSInteger directChatsIndex;
|
|
|
|
/**
|
|
Devices
|
|
*/
|
|
NSArray<MXDeviceInfo *> *devicesArray;
|
|
NSInteger devicesIndex;
|
|
EncryptionInfoView *encryptionInfoView;
|
|
|
|
/**
|
|
Observe UIApplicationWillChangeStatusBarOrientationNotification to hide/show bubbles bg.
|
|
*/
|
|
id UIApplicationWillChangeStatusBarOrientationNotificationObserver;
|
|
}
|
|
@end
|
|
|
|
@implementation RoomMemberDetailsViewController
|
|
|
|
#pragma mark - Class methods
|
|
|
|
+ (UINib *)nib
|
|
{
|
|
return [UINib nibWithNibName:NSStringFromClass(self.class)
|
|
bundle:[NSBundle bundleForClass:self.class]];
|
|
}
|
|
|
|
+ (instancetype)roomMemberDetailsViewController
|
|
{
|
|
return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
|
|
bundle:[NSBundle bundleForClass:self.class]];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)finalizeInit
|
|
{
|
|
[super finalizeInit];
|
|
|
|
// Setup `MXKViewControllerHandling` properties
|
|
self.defaultBarTintColor = kRiotNavBarTintColor;
|
|
self.enableBarTintColorStatusChange = NO;
|
|
self.rageShakeManager = [RageShakeManager sharedManager];
|
|
|
|
adminActionsArray = [[NSMutableArray alloc] init];
|
|
otherActionsArray = [[NSMutableArray alloc] init];
|
|
directChatsArray = [[NSMutableArray alloc] init];
|
|
}
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
[super viewDidLoad];
|
|
// Do any additional setup after loading the view, typically from a nib.
|
|
|
|
self.memberHeaderView.backgroundColor = kRiotColorLightGrey;
|
|
self.roomMemberNameLabel.textColor = kRiotTextColorBlack;
|
|
self.roomMemberStatusLabel.textColor = kRiotColorGreen;
|
|
|
|
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
|
|
[tap setNumberOfTouchesRequired:1];
|
|
[tap setNumberOfTapsRequired:1];
|
|
[tap setDelegate:self];
|
|
[self.roomMemberNameLabelMask addGestureRecognizer:tap];
|
|
self.roomMemberNameLabelMask.userInteractionEnabled = YES;
|
|
|
|
self.navigationItem.titleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 600, 40)];
|
|
|
|
memberTitleView = [RoomMemberTitleView roomMemberTitleView];
|
|
self.memberThumbnail = memberTitleView.memberAvatar;
|
|
|
|
// Add tap to show the room member avatar in fullscreen
|
|
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
|
|
[tap setNumberOfTouchesRequired:1];
|
|
[tap setNumberOfTapsRequired:1];
|
|
[tap setDelegate:self];
|
|
[self.memberThumbnail addGestureRecognizer:tap];
|
|
self.memberThumbnail.userInteractionEnabled = YES;
|
|
|
|
// Need to listen tap gesture on the area part of the avatar image that is outside
|
|
// of the navigation bar, its parent but smaller view.
|
|
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
|
|
[tap setNumberOfTouchesRequired:1];
|
|
[tap setNumberOfTapsRequired:1];
|
|
[tap setDelegate:self];
|
|
[self.roomMemberAvatarMask addGestureRecognizer:tap];
|
|
self.roomMemberAvatarMask.userInteractionEnabled = YES;
|
|
|
|
// Add the title view and define edge constraints
|
|
memberTitleView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.navigationItem.titleView addSubview:memberTitleView];
|
|
|
|
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView
|
|
attribute:NSLayoutAttributeTop
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.navigationItem.titleView
|
|
attribute:NSLayoutAttributeTop
|
|
multiplier:1.0f
|
|
constant:0.0f];
|
|
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView
|
|
attribute:NSLayoutAttributeBottom
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.navigationItem.titleView
|
|
attribute:NSLayoutAttributeBottom
|
|
multiplier:1.0f
|
|
constant:0.0f];
|
|
NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView
|
|
attribute:NSLayoutAttributeLeading
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.navigationItem.titleView
|
|
attribute:NSLayoutAttributeLeading
|
|
multiplier:1.0f
|
|
constant:0.0f];
|
|
NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView
|
|
attribute:NSLayoutAttributeTrailing
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.navigationItem.titleView
|
|
attribute:NSLayoutAttributeTrailing
|
|
multiplier:1.0f
|
|
constant:0.0f];
|
|
[NSLayoutConstraint activateConstraints:@[topConstraint, bottomConstraint, leadingConstraint, trailingConstraint]];
|
|
|
|
// Register collection view cell class
|
|
[self.tableView registerClass:TableViewCellWithButton.class forCellReuseIdentifier:[TableViewCellWithButton defaultReuseIdentifier]];
|
|
[self.tableView registerClass:RoomTableViewCell.class forCellReuseIdentifier:[RoomTableViewCell defaultReuseIdentifier]];
|
|
[self.tableView registerClass:DeviceTableViewCell.class forCellReuseIdentifier:[DeviceTableViewCell defaultReuseIdentifier]];
|
|
|
|
// Hide line separators of empty cells
|
|
self.tableView.tableFooterView = [[UIView alloc] init];
|
|
|
|
// Observe UIApplicationWillChangeStatusBarOrientationNotification to hide/show bubbles bg.
|
|
UIApplicationWillChangeStatusBarOrientationNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillChangeStatusBarOrientationNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
NSNumber *orientation = (NSNumber*)(notif.userInfo[UIApplicationStatusBarOrientationUserInfoKey]);
|
|
self.bottomImageView.hidden = (orientation.integerValue == UIInterfaceOrientationLandscapeLeft || orientation.integerValue == UIInterfaceOrientationLandscapeRight);
|
|
}];
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
{
|
|
[super viewWillAppear:animated];
|
|
|
|
// Screen tracking (via Google Analytics)
|
|
id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
|
|
if (tracker)
|
|
{
|
|
[tracker set:kGAIScreenName value:@"RoomMemberDetails"];
|
|
[tracker send:[[GAIDictionaryBuilder createScreenView] build]];
|
|
}
|
|
|
|
// Hide the bottom border of the navigation bar to display the expander header
|
|
[self hideNavigationBarBorder:YES];
|
|
|
|
// Handle here the bottom image visibility
|
|
UIInterfaceOrientation screenOrientation = [[UIApplication sharedApplication] statusBarOrientation];
|
|
self.bottomImageView.hidden = (screenOrientation == UIInterfaceOrientationLandscapeLeft || screenOrientation == UIInterfaceOrientationLandscapeRight);
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated
|
|
{
|
|
[super viewWillDisappear:animated];
|
|
|
|
// Restore navigation bar display
|
|
[self hideNavigationBarBorder:NO];
|
|
|
|
self.bottomImageView.hidden = YES;
|
|
}
|
|
|
|
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
|
|
{
|
|
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
|
|
|
// Restore navigation bar display
|
|
[self hideNavigationBarBorder:NO];
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(coordinator.transitionDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
// Hide the bottom border of the navigation bar
|
|
[self hideNavigationBarBorder:YES];
|
|
|
|
});
|
|
}
|
|
|
|
- (void)destroy
|
|
{
|
|
[super destroy];
|
|
|
|
adminActionsArray = nil;
|
|
otherActionsArray = nil;
|
|
directChatsArray = nil;
|
|
devicesArray = nil;
|
|
|
|
if (UIApplicationWillChangeStatusBarOrientationNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:UIApplicationWillChangeStatusBarOrientationNotificationObserver];
|
|
UIApplicationWillChangeStatusBarOrientationNotificationObserver = nil;
|
|
}
|
|
|
|
[memberTitleView removeFromSuperview];
|
|
memberTitleView = nil;
|
|
}
|
|
|
|
- (void)viewDidLayoutSubviews
|
|
{
|
|
[super viewDidLayoutSubviews];
|
|
|
|
// Check here whether a subview has been added or removed
|
|
if (encryptionInfoView)
|
|
{
|
|
if (!encryptionInfoView.superview)
|
|
{
|
|
// Reset
|
|
encryptionInfoView = nil;
|
|
|
|
// Reload the full table to take into account a potential change on a device status.
|
|
[self updateMemberInfo];
|
|
}
|
|
}
|
|
|
|
if (memberTitleView)
|
|
{
|
|
// Adjust the header height by taking into account the actual position of the member avatar in title view
|
|
// This position depends automatically on the screen orientation.
|
|
CGRect memberAvatarFrame = memberTitleView.memberAvatar.frame;
|
|
CGPoint memberAvatarActualPosition = [memberTitleView convertPoint:memberAvatarFrame.origin toView:self.view];
|
|
|
|
CGFloat avatarHeaderHeight = memberAvatarActualPosition.y + memberAvatarFrame.size.height;
|
|
if (_roomMemberAvatarHeaderBackgroundHeightConstraint.constant != avatarHeaderHeight)
|
|
{
|
|
_roomMemberAvatarHeaderBackgroundHeightConstraint.constant = avatarHeaderHeight;
|
|
|
|
// Force the layout of the header
|
|
[self.memberHeaderView layoutIfNeeded];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (UIImage*)picturePlaceholder
|
|
{
|
|
if (self.mxRoomMember)
|
|
{
|
|
// Use the vector style placeholder
|
|
return [AvatarGenerator generateAvatarForMatrixItem:self.mxRoomMember.userId withDisplayName:self.mxRoomMember.displayname];
|
|
}
|
|
|
|
return [UIImage imageNamed:@"placeholder"];
|
|
}
|
|
|
|
- (void)updateMemberInfo
|
|
{
|
|
if (self.mxRoomMember)
|
|
{
|
|
self.roomMemberNameLabel.text = self.mxRoomMember.displayname ? self.mxRoomMember.displayname : self.mxRoomMember.userId;
|
|
|
|
// Update member badge
|
|
MXRoomPowerLevels *powerLevels = [self.mxRoom.state powerLevels];
|
|
NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId];
|
|
if (powerLevel >= kRiotRoomAdminLevel)
|
|
{
|
|
memberTitleView.memberBadge.image = [UIImage imageNamed:@"admin_icon"];
|
|
memberTitleView.memberBadge.hidden = NO;
|
|
}
|
|
else if (powerLevel >= kRiotRoomModeratorLevel)
|
|
{
|
|
memberTitleView.memberBadge.image = [UIImage imageNamed:@"mod_icon"];
|
|
memberTitleView.memberBadge.hidden = NO;
|
|
}
|
|
else
|
|
{
|
|
memberTitleView.memberBadge.hidden = YES;
|
|
}
|
|
|
|
NSString* presenceText;
|
|
|
|
if (self.mxRoomMember.userId)
|
|
{
|
|
MXUser *user = [self.mxRoom.mxSession userWithUserId:self.mxRoomMember.userId];
|
|
presenceText = [Tools presenceText:user];
|
|
}
|
|
|
|
self.roomMemberStatusLabel.text = presenceText;
|
|
|
|
// Retrieve the existing direct chats
|
|
[directChatsArray removeAllObjects];
|
|
NSArray *directRoomIds = self.mainSession.directRooms[self.mxRoomMember.userId];
|
|
// Check whether the room is still existing
|
|
for (NSString* directRoomId in directRoomIds)
|
|
{
|
|
if ([self.mainSession roomWithRoomId:directRoomId])
|
|
{
|
|
[directChatsArray addObject:directRoomId];
|
|
}
|
|
}
|
|
|
|
// Retrieve member's devices
|
|
[self.mxRoom.mxSession.crypto devicesForUser:self.mxRoomMember.userId complete:^(NSArray<MXDeviceInfo *> *devices) {
|
|
devicesArray = devices;
|
|
|
|
// Reload the full table to take into account a potential change on a device status.
|
|
[super updateMemberInfo];
|
|
}];
|
|
}
|
|
|
|
// Complete data update and reload table view
|
|
[super updateMemberInfo];
|
|
}
|
|
|
|
#pragma mark - Hide/Show navigation bar border
|
|
|
|
- (void)hideNavigationBarBorder:(BOOL)isHidden
|
|
{
|
|
// Consider the main navigation controller if the current view controller is embedded inside a split view controller.
|
|
UINavigationController *mainNavigationController = self.navigationController;
|
|
if (self.splitViewController && self.splitViewController.isCollapsed && self.splitViewController.viewControllers.count)
|
|
{
|
|
mainNavigationController = self.splitViewController.viewControllers.firstObject;
|
|
}
|
|
|
|
if (isHidden)
|
|
{
|
|
// The default shadow image is nil. When non-nil, this property represents a custom shadow image to show instead
|
|
// of the default. For a custom shadow image to be shown, a custom background image must also be set with the
|
|
// setBackgroundImage:forBarMetrics: method. If the default background image is used, then the default shadow
|
|
// image will be used regardless of the value of this property.
|
|
[mainNavigationController.navigationBar setShadowImage:[[UIImage alloc] init]];
|
|
[mainNavigationController.navigationBar setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];
|
|
}
|
|
else
|
|
{
|
|
// Restore default navigationbar settings
|
|
[mainNavigationController.navigationBar setShadowImage:nil];
|
|
[mainNavigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
|
|
}
|
|
}
|
|
|
|
#pragma mark - TableView data source
|
|
|
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
|
{
|
|
NSInteger sectionCount = 0;
|
|
|
|
BOOL isOneself = NO;
|
|
|
|
// Check user's power level before allowing an action (kick, ban, ...)
|
|
MXRoomPowerLevels *powerLevels = [self.mxRoom.state powerLevels];
|
|
NSInteger memberPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId];
|
|
NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId];
|
|
|
|
[adminActionsArray removeAllObjects];
|
|
[otherActionsArray removeAllObjects];
|
|
|
|
// Consider the case of the user himself
|
|
if ([self.mxRoomMember.userId isEqualToString:self.mainSession.myUser.userId])
|
|
{
|
|
isOneself = YES;
|
|
|
|
[otherActionsArray addObject:@(MXKRoomMemberDetailsActionLeave)];
|
|
|
|
if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels])
|
|
{
|
|
// Check whether the user is admin (in this case he may reduce his power level to become moderator or less, EXCEPT if he is the only admin).
|
|
if (oneSelfPowerLevel >= kRiotRoomAdminLevel)
|
|
{
|
|
NSArray *levelValues = powerLevels.users.allValues;
|
|
NSUInteger adminCount = 0;
|
|
for (NSNumber *valueNumber in levelValues)
|
|
{
|
|
if ([valueNumber unsignedIntegerValue] >= kRiotRoomAdminLevel)
|
|
{
|
|
adminCount ++;
|
|
}
|
|
}
|
|
|
|
if (adminCount > 1)
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionSetModerator)];
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionSetDefaultPowerLevel)];
|
|
}
|
|
}
|
|
// Check whether the user is moderator (in this case he may reduce his power level to become normal user).
|
|
else if (oneSelfPowerLevel >= kRiotRoomModeratorLevel)
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionSetDefaultPowerLevel)];
|
|
}
|
|
}
|
|
}
|
|
else if (self.mxRoomMember)
|
|
{
|
|
// Enumerate admin actions
|
|
switch (self.mxRoomMember.membership)
|
|
{
|
|
case MXMembershipInvite:
|
|
case MXMembershipJoin:
|
|
{
|
|
// update power level
|
|
if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels] && oneSelfPowerLevel > memberPowerLevel)
|
|
{
|
|
// Check whether user is admin
|
|
if (oneSelfPowerLevel >= kRiotRoomAdminLevel)
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionSetAdmin)];
|
|
}
|
|
|
|
// Check whether the member may become moderator
|
|
if (oneSelfPowerLevel >= kRiotRoomModeratorLevel && memberPowerLevel < kRiotRoomModeratorLevel)
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionSetModerator)];
|
|
}
|
|
|
|
if (memberPowerLevel >= kRiotRoomModeratorLevel)
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionSetDefaultPowerLevel)];
|
|
}
|
|
}
|
|
|
|
// Check conditions to be able to kick someone
|
|
if (oneSelfPowerLevel >= [powerLevels kick] && oneSelfPowerLevel > memberPowerLevel)
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionKick)];
|
|
}
|
|
// Check conditions to be able to ban someone
|
|
if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel > memberPowerLevel)
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionBan)];
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MXMembershipLeave:
|
|
{
|
|
// Check conditions to be able to invite someone
|
|
if (oneSelfPowerLevel >= [powerLevels invite])
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionInvite)];
|
|
}
|
|
// Check conditions to be able to ban someone
|
|
if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel > memberPowerLevel)
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionBan)];
|
|
}
|
|
break;
|
|
}
|
|
case MXMembershipBan:
|
|
{
|
|
// Check conditions to be able to unban someone
|
|
if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel > memberPowerLevel)
|
|
{
|
|
[adminActionsArray addObject:@(MXKRoomMemberDetailsActionUnban)];
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// List the other actions
|
|
if (self.enableVoipCall)
|
|
{
|
|
// Offer voip call options
|
|
[otherActionsArray addObject:@(MXKRoomMemberDetailsActionStartVoiceCall)];
|
|
[otherActionsArray addObject:@(MXKRoomMemberDetailsActionStartVideoCall)];
|
|
}
|
|
|
|
// Check whether the option Ignore may be presented
|
|
if (self.mxRoomMember.membership == MXMembershipJoin)
|
|
{
|
|
// is he already ignored ?
|
|
if (![self.mainSession isUserIgnored:self.mxRoomMember.userId])
|
|
{
|
|
[otherActionsArray addObject:@(MXKRoomMemberDetailsActionIgnore)];
|
|
}
|
|
else
|
|
{
|
|
[otherActionsArray addObject:@(MXKRoomMemberDetailsActionUnignore)];
|
|
}
|
|
}
|
|
|
|
if (self.enableMention)
|
|
{
|
|
// Add mention option
|
|
[otherActionsArray addObject:@(MXKRoomMemberDetailsActionMention)];
|
|
}
|
|
}
|
|
|
|
adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1;
|
|
|
|
if (otherActionsArray.count)
|
|
{
|
|
otherActionsIndex = sectionCount++;
|
|
}
|
|
if (adminActionsArray.count)
|
|
{
|
|
adminToolsIndex = sectionCount++;
|
|
}
|
|
|
|
if (!isOneself)
|
|
{
|
|
directChatsIndex = sectionCount++;
|
|
}
|
|
|
|
if (devicesArray.count)
|
|
{
|
|
devicesIndex = sectionCount++;
|
|
}
|
|
|
|
return sectionCount;
|
|
}
|
|
|
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
|
{
|
|
if (section == adminToolsIndex)
|
|
{
|
|
return adminActionsArray.count;
|
|
}
|
|
else if (section == otherActionsIndex)
|
|
{
|
|
return otherActionsArray.count;
|
|
}
|
|
else if (section == directChatsIndex)
|
|
{
|
|
return (directChatsArray.count + 1);
|
|
}
|
|
else if (section == devicesIndex)
|
|
{
|
|
return (devicesArray.count);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
|
{
|
|
if (section == adminToolsIndex)
|
|
{
|
|
return NSLocalizedStringFromTable(@"room_participants_action_section_admin_tools", @"Vector", nil);
|
|
}
|
|
else if (section == directChatsIndex)
|
|
{
|
|
return NSLocalizedStringFromTable(@"room_participants_action_section_direct_chats", @"Vector", nil);
|
|
}
|
|
else if (section == devicesIndex)
|
|
{
|
|
return NSLocalizedStringFromTable(@"room_participants_action_section_devices", @"Vector", nil);
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (NSString*)actionButtonTitle:(MXKRoomMemberDetailsAction)action
|
|
{
|
|
NSString *title;
|
|
|
|
switch (action)
|
|
{
|
|
case MXKRoomMemberDetailsActionInvite:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_invite", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionLeave:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_leave", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionKick:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_remove", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionBan:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_ban", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionUnban:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_unban", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionIgnore:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_ignore", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionUnignore:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_unignore", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionSetDefaultPowerLevel:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_set_default_power_level", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionSetModerator:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_set_moderator", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionSetAdmin:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_set_admin", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionStartChat:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_start_chat", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionStartVoiceCall:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_start_voice_call", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionStartVideoCall:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_start_video_call", @"Vector", nil);
|
|
break;
|
|
case MXKRoomMemberDetailsActionMention:
|
|
title = NSLocalizedStringFromTable(@"room_participants_action_mention", @"Vector", nil);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return title;
|
|
}
|
|
|
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
UITableViewCell *cell;
|
|
|
|
if (indexPath.section == adminToolsIndex || indexPath.section == otherActionsIndex)
|
|
{
|
|
TableViewCellWithButton *cellWithButton = [tableView dequeueReusableCellWithIdentifier:[TableViewCellWithButton defaultReuseIdentifier] forIndexPath:indexPath];
|
|
|
|
NSNumber *actionNumber;
|
|
if (indexPath.section == adminToolsIndex && indexPath.row < adminActionsArray.count)
|
|
{
|
|
actionNumber = [adminActionsArray objectAtIndex:indexPath.row];
|
|
}
|
|
else if (indexPath.section == otherActionsIndex && indexPath.row < otherActionsArray.count)
|
|
{
|
|
actionNumber = [otherActionsArray objectAtIndex:indexPath.row];
|
|
}
|
|
|
|
if (actionNumber)
|
|
{
|
|
NSString *title = [self actionButtonTitle:actionNumber.unsignedIntegerValue];
|
|
|
|
[cellWithButton.mxkButton setTitle:title forState:UIControlStateNormal];
|
|
[cellWithButton.mxkButton setTitle:title forState:UIControlStateHighlighted];
|
|
|
|
if (actionNumber.unsignedIntegerValue == MXKRoomMemberDetailsActionKick)
|
|
{
|
|
[cellWithButton.mxkButton setTitleColor:kRiotColorPinkRed forState:UIControlStateNormal];
|
|
[cellWithButton.mxkButton setTitleColor:kRiotColorPinkRed forState:UIControlStateHighlighted];
|
|
}
|
|
else
|
|
{
|
|
[cellWithButton.mxkButton setTitleColor:kRiotTextColorBlack forState:UIControlStateNormal];
|
|
[cellWithButton.mxkButton setTitleColor:kRiotTextColorBlack forState:UIControlStateHighlighted];
|
|
}
|
|
|
|
[cellWithButton.mxkButton addTarget:self action:@selector(onActionButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
cellWithButton.mxkButton.tag = actionNumber.unsignedIntegerValue;
|
|
}
|
|
|
|
cell = cellWithButton;
|
|
}
|
|
else if (indexPath.section == directChatsIndex)
|
|
{
|
|
RoomTableViewCell *roomCell = [tableView dequeueReusableCellWithIdentifier:[RoomTableViewCell defaultReuseIdentifier] forIndexPath:indexPath];
|
|
|
|
if (indexPath.row < directChatsArray.count)
|
|
{
|
|
MXRoom *room = [self.mainSession roomWithRoomId:directChatsArray[indexPath.row]];
|
|
if (room)
|
|
{
|
|
[roomCell render:room];
|
|
}
|
|
roomCell.directRoomIcon.hidden = NO;
|
|
roomCell.encryptedRoomIcon.hidden = !room.state.isEncrypted;
|
|
}
|
|
else
|
|
{
|
|
roomCell.directRoomIcon.hidden = YES;
|
|
|
|
roomCell.avatarImageView.image = [UIImage imageNamed:@"start_chat"];
|
|
roomCell.avatarImageView.backgroundColor = [UIColor clearColor];
|
|
roomCell.avatarImageView.userInteractionEnabled = NO;
|
|
roomCell.titleLabel.text = NSLocalizedStringFromTable(@"room_participants_action_start_new_chat", @"Vector", nil);
|
|
}
|
|
|
|
cell = roomCell;
|
|
}
|
|
else if (indexPath.section == devicesIndex)
|
|
{
|
|
DeviceTableViewCell *deviceCell = [tableView dequeueReusableCellWithIdentifier:[DeviceTableViewCell defaultReuseIdentifier] forIndexPath:indexPath];
|
|
deviceCell.selectionStyle = UITableViewCellSelectionStyleNone;
|
|
|
|
if (indexPath.row < devicesArray.count)
|
|
{
|
|
MXDeviceInfo *deviceInfo = devicesArray[indexPath.row];
|
|
[deviceCell render:deviceInfo];
|
|
deviceCell.delegate = self;
|
|
|
|
// Display here the Verify and Block buttons except if the device is the current one.
|
|
deviceCell.verifyButton.hidden = deviceCell.blockButton.hidden = [deviceInfo.deviceId isEqualToString:self.mxRoom.mxSession.matrixRestClient.credentials.deviceId];
|
|
}
|
|
cell = deviceCell;
|
|
}
|
|
else
|
|
{
|
|
// Create a fake cell to prevent app from crashing
|
|
cell = [[UITableViewCell alloc] init];
|
|
}
|
|
|
|
return cell;
|
|
}
|
|
|
|
#pragma mark - UITableView delegate
|
|
|
|
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
if (indexPath.section == directChatsIndex)
|
|
{
|
|
return [RoomTableViewCell cellHeight];
|
|
}
|
|
else if (indexPath.section == devicesIndex)
|
|
{
|
|
if (indexPath.row < devicesArray.count)
|
|
{
|
|
return [DeviceTableViewCell cellHeightWithDeviceInfo:devicesArray[indexPath.row] andCellWidth:self.tableView.frame.size.width];
|
|
}
|
|
}
|
|
|
|
return TABLEVIEW_ROW_CELL_HEIGHT;
|
|
}
|
|
|
|
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
|
{
|
|
if (section == otherActionsIndex)
|
|
{
|
|
return TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN;
|
|
}
|
|
|
|
return TABLEVIEW_SECTION_HEADER_HEIGHT;
|
|
}
|
|
|
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath
|
|
{
|
|
if (indexPath.section == directChatsIndex)
|
|
{
|
|
if (indexPath.row < directChatsArray.count)
|
|
{
|
|
// Open this room
|
|
[[AppDelegate theDelegate] showRoom:directChatsArray[indexPath.row] andEventId:nil withMatrixSession:self.mainSession];
|
|
}
|
|
else
|
|
{
|
|
// Create a new direct chat with the member
|
|
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
button.tag = MXKRoomMemberDetailsActionStartChat;
|
|
|
|
[super onActionButtonPressed:button];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
|
|
if (selectedCell && [selectedCell isKindOfClass:TableViewCellWithButton.class])
|
|
{
|
|
TableViewCellWithButton *cell = (TableViewCellWithButton*)selectedCell;
|
|
|
|
[self onActionButtonPressed:cell.mxkButton];
|
|
}
|
|
}
|
|
|
|
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
|
}
|
|
|
|
#pragma mark - Action
|
|
|
|
- (void)onActionButtonPressed:(id)sender
|
|
{
|
|
if ([sender isKindOfClass:[UIButton class]])
|
|
{
|
|
// already a pending action
|
|
if ([self hasPendingAction])
|
|
{
|
|
return;
|
|
}
|
|
|
|
UIButton *button = (UIButton*)sender;
|
|
|
|
switch (button.tag)
|
|
{
|
|
case MXKRoomMemberDetailsActionSetDefaultPowerLevel:
|
|
{
|
|
[self setPowerLevel:self.mxRoom.state.powerLevels.usersDefault promptUser:YES];
|
|
break;
|
|
}
|
|
case MXKRoomMemberDetailsActionSetModerator:
|
|
{
|
|
[self setPowerLevel:kRiotRoomModeratorLevel promptUser:YES];
|
|
break;
|
|
}
|
|
case MXKRoomMemberDetailsActionSetAdmin:
|
|
{
|
|
[self setPowerLevel:kRiotRoomAdminLevel promptUser:YES];
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
[super onActionButtonPressed:sender];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)handleTapGesture:(UITapGestureRecognizer*)tapGestureRecognizer
|
|
{
|
|
UIView *view = tapGestureRecognizer.view;
|
|
|
|
if (view == self.roomMemberNameLabelMask && self.mxRoomMember.displayname)
|
|
{
|
|
if ([self.roomMemberNameLabel.text isEqualToString:self.mxRoomMember.displayname])
|
|
{
|
|
// Display room member matrix id
|
|
self.roomMemberNameLabel.text = self.mxRoomMember.userId;
|
|
}
|
|
else
|
|
{
|
|
// Restore display name
|
|
self.roomMemberNameLabel.text = self.mxRoomMember.displayname;
|
|
}
|
|
}
|
|
else if (view == self.memberThumbnail || view == self.roomMemberAvatarMask)
|
|
{
|
|
// Show the avatar in full screen
|
|
__block MXKImageView * avatarFullScreenView = [[MXKImageView alloc] initWithFrame:CGRectZero];
|
|
avatarFullScreenView.stretchable = YES;
|
|
|
|
[avatarFullScreenView setRightButtonTitle:[NSBundle mxk_localizedStringForKey:@"ok"] handler:^(MXKImageView* imageView, NSString* buttonTitle) {
|
|
[avatarFullScreenView dismissSelection];
|
|
[avatarFullScreenView removeFromSuperview];
|
|
|
|
avatarFullScreenView = nil;
|
|
}];
|
|
|
|
NSString *avatarURL = nil;
|
|
if (self.mxRoomMember.avatarUrl)
|
|
{
|
|
avatarURL = [self.mainSession.matrixRestClient urlOfContent:self.mxRoomMember.avatarUrl];
|
|
}
|
|
|
|
[avatarFullScreenView setImageURL:avatarURL
|
|
withType:nil
|
|
andImageOrientation:UIImageOrientationUp
|
|
previewImage:self.memberThumbnail.image];
|
|
|
|
[avatarFullScreenView showFullScreen];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)deviceTableViewCell:(DeviceTableViewCell*)deviceTableViewCell updateDeviceVerification:(MXDeviceVerification)verificationStatus
|
|
{
|
|
if (verificationStatus == MXDeviceVerified)
|
|
{
|
|
// Prompt the user before marking as verified the device.
|
|
encryptionInfoView = [[EncryptionInfoView alloc] initWithDeviceInfo:deviceTableViewCell.deviceInfo andMatrixSession:self.mxRoom.mxSession];
|
|
[encryptionInfoView onButtonPressed:encryptionInfoView.verifyButton];
|
|
|
|
// Add shadow on added view
|
|
encryptionInfoView.layer.cornerRadius = 5;
|
|
encryptionInfoView.layer.shadowOffset = CGSizeMake(0, 1);
|
|
encryptionInfoView.layer.shadowOpacity = 0.5f;
|
|
|
|
// Add the view and define edge constraints
|
|
[self.view addSubview:encryptionInfoView];
|
|
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
|
|
attribute:NSLayoutAttributeTop
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.tableView
|
|
attribute:NSLayoutAttributeTop
|
|
multiplier:1.0f
|
|
constant:10.0f]];
|
|
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
|
|
attribute:NSLayoutAttributeBottom
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.tableView
|
|
attribute:NSLayoutAttributeBottom
|
|
multiplier:1.0f
|
|
constant:-10.0f]];
|
|
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.tableView
|
|
attribute:NSLayoutAttributeLeading
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:encryptionInfoView
|
|
attribute:NSLayoutAttributeLeading
|
|
multiplier:1.0f
|
|
constant:-10.0f]];
|
|
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.tableView
|
|
attribute:NSLayoutAttributeTrailing
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:encryptionInfoView
|
|
attribute:NSLayoutAttributeTrailing
|
|
multiplier:1.0f
|
|
constant:10.0f]];
|
|
[self.view setNeedsUpdateConstraints];
|
|
}
|
|
else
|
|
{
|
|
[self.mxRoom.mxSession.crypto setDeviceVerification:verificationStatus
|
|
forDevice:deviceTableViewCell.deviceInfo.deviceId
|
|
ofUser:self.mxRoomMember.userId
|
|
success:^{
|
|
[self updateMemberInfo];
|
|
} failure:nil];
|
|
}
|
|
}
|
|
|
|
@end
|