element-ios/Riot/Modules/Contacts/Details/ContactDetailsViewController.m

1180 lines
43 KiB
Objective-C

/*
Copyright 2018-2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd
Copyright 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
#import "ContactDetailsViewController.h"
#import "GeneratedInterface-Swift.h"
#import "MXSession+Riot.h"
#import "RoomMemberTitleView.h"
#import "AvatarGenerator.h"
#import "Tools.h"
#import "TableViewCellWithButton.h"
#import "RoomTableViewCell.h"
#import "TableViewCellWithButton.h"
#import "GBDeviceInfo_iOS.h"
#define TABLEVIEW_ROW_CELL_HEIGHT 46
#define TABLEVIEW_SECTION_HEADER_HEIGHT 28
#define TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN 0.01f
@interface ContactDetailsViewController () <RoomMemberTitleViewDelegate>
{
RoomMemberTitleView* contactTitleView;
// HTTP Request
MXHTTPOperation *roomCreationRequest;
/**
Observe UIApplicationWillChangeStatusBarOrientationNotification to hide/show bubbles bg.
*/
__weak id UIApplicationWillChangeStatusBarOrientationNotificationObserver;
/**
The observer of the presence for matrix user.
*/
__weak id mxPresenceObserver;
/**
List of the basic actions on this contact.
*/
NSMutableArray<NSNumber*> *actionsArray;
NSInteger actionsIndex;
/**
List of the direct chats (room ids) with this contact.
*/
NSMutableArray<NSString*> *directChatsArray;
NSInteger directChatsIndex;
/**
mask view while processing a request
*/
UIActivityIndicatorView * pendingMaskSpinnerView;
/**
Current alert (if any).
*/
UIAlertController *currentAlert;
/**
Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
*/
__weak id kThemeServiceDidChangeThemeNotificationObserver;
/**
The current visibility of the status bar in this view controller.
*/
BOOL isStatusBarHidden;
}
@end
@implementation ContactDetailsViewController
#pragma mark - Class methods
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass(self.class)
bundle:[NSBundle bundleForClass:self.class]];
}
+ (instancetype)instantiate
{
return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
bundle:[NSBundle bundleForClass:self.class]];
}
#pragma mark -
- (void)finalizeInit
{
[super finalizeInit];
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
// Keep visible the status bar by default.
isStatusBarHidden = NO;
}
- (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 (!_tableView)
{
// Instantiate view controller objects
[[[self class] nib] instantiateWithOwner:self options:nil];
}
actionsArray = [[NSMutableArray alloc] init];
directChatsArray = [[NSMutableArray alloc] init];
contactTitleView = [RoomMemberTitleView roomMemberTitleView];
contactTitleView.delegate = self;
self.contactAvatar.contentMode = UIViewContentModeScaleAspectFill;
self.contactAvatar.defaultBackgroundColor = [UIColor clearColor];
// Define directly the navigation titleView with the custom title view instance. Do not use anymore a container.
self.navigationItem.titleView = contactTitleView;
// Display leftBarButtonItems or leftBarButtonItem to the right of the Back button
self.navigationItem.leftItemsSupplementBackButton = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[tap setDelegate:self];
[self.contactNameLabelMask addGestureRecognizer:tap];
self.contactNameLabelMask.userInteractionEnabled = YES;
// Add tap to show the contact avatar in fullscreen
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[tap setDelegate:self];
[self.contactAvatarMask addGestureRecognizer:tap];
self.contactAvatarMask.userInteractionEnabled = YES;
// Need to listen to the tap gesture in the title view too.
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[tap setDelegate:self];
[contactTitleView.memberAvatarMask addGestureRecognizer:tap];
contactTitleView.memberAvatarMask.userInteractionEnabled = YES;
// Register collection view cell class
[self.tableView registerClass:TableViewCellWithButton.class forCellReuseIdentifier:[TableViewCellWithButton defaultReuseIdentifier]];
[self.tableView registerClass:RoomTableViewCell.class forCellReuseIdentifier:[RoomTableViewCell 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);
}];
MXWeakify(self);
// Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
MXStrongifyAndReturnIfNil(self);
[self userInterfaceThemeDidChange];
}];
[self userInterfaceThemeDidChange];
}
- (void)userInterfaceThemeDidChange
{
[ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar];
self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor;
self.headerView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor;
self.contactNameLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
self.contactStatusLabel.textColor = ThemeService.shared.theme.tintColor;
// 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;
}
- (BOOL)prefersStatusBarHidden
{
// Return the current status bar visibility.
return isStatusBarHidden;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// 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);
// Report matrix session from AppDelegate
NSArray *mxSessions = [AppDelegate theDelegate].mxSessions;
for (MXSession *mxSession in mxSessions)
{
[self addMatrixSession:mxSession];
}
if (_contact)
{
// Register on notifications related to the contact change
[self registerOnContactChangeNotifications];
// Force refresh
[self refreshContactDetails];
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self cancelRegistrationOnContactChangeNotifications];
// 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];
if (kThemeServiceDidChangeThemeNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver];
kThemeServiceDidChangeThemeNotificationObserver = nil;
}
if (roomCreationRequest)
{
[roomCreationRequest cancel];
roomCreationRequest = nil;
}
[self cancelRegistrationOnContactChangeNotifications];
if (UIApplicationWillChangeStatusBarOrientationNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:UIApplicationWillChangeStatusBarOrientationNotificationObserver];
UIApplicationWillChangeStatusBarOrientationNotificationObserver = nil;
}
[contactTitleView removeFromSuperview];
contactTitleView = nil;
actionsArray = nil;
directChatsArray = nil;
[self removePendingActionMask];
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert = nil;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// Check whether the title view has been created and rendered.
if (contactTitleView && contactTitleView.superview)
{
// 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.
CGPoint memberAvatarOriginInTitleView = contactTitleView.memberAvatarMask.frame.origin;
CGPoint memberAvatarActualPosition = [contactTitleView convertPoint:memberAvatarOriginInTitleView toView:self.view];
CGFloat avatarHeaderHeight = memberAvatarActualPosition.y + self.contactAvatar.frame.size.height;
if (_contactAvatarHeaderBackgroundHeightConstraint.constant != avatarHeaderHeight)
{
_contactAvatarHeaderBackgroundHeightConstraint.constant = avatarHeaderHeight;
// Force the layout of the header
[self.headerView layoutIfNeeded];
}
}
}
#pragma mark -
- (void)setContact:(MXKContact *)contact
{
[self cancelRegistrationOnContactChangeNotifications];
_contact = contact;
[self registerOnContactChangeNotifications];
if (!_contact.isMatrixContact)
{
// Refresh matrix info of the contact
[[MXKContactManager sharedManager] updateMatrixIDsForLocalContact:_contact];
}
[self refreshContactDetails];
}
- (void)setEnableVoipCall:(BOOL)enableVoipCall
{
if (_enableVoipCall != enableVoipCall)
{
_enableVoipCall = enableVoipCall;
// Refresh displayed options
[self.tableView reloadData];
}
}
#pragma mark -
- (void)registerOnContactChangeNotifications
{
// Be warned when the thumbnail is updated
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThumbnailUpdate:) name:kMXKContactThumbnailUpdateNotification object:nil];
MXWeakify(self);
// Observe contact presence change
mxPresenceObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKContactManagerMatrixUserPresenceChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
MXStrongifyAndReturnIfNil(self);
NSString* matrixId = self.firstMatrixId;
if (matrixId && [matrixId isEqualToString:notif.object])
{
[self refreshContactPresence];
}
}];
// Observe 'MXKContactManager' notifications
if (_contact.isMatrixContact)
{
// Observe 'MXKContactManager' notification on Matrix contacts to refresh details.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactManagerNotification:) name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
}
else
{
// Observe 'MXKContactManager' notifications on Local contacts to refresh details.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactManagerNotification:) name:kMXKContactManagerDidUpdateLocalContactsNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactManagerNotification:) name:kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification object:nil];
}
}
- (void)cancelRegistrationOnContactChangeNotifications
{
// Remove any pending observers
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (mxPresenceObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:mxPresenceObserver];
mxPresenceObserver = nil;
}
}
- (void)onContactManagerNotification:(NSNotification *)notif
{
// Check whether a contact Id is provided
if (notif.object)
{
NSString* contactID = notif.object;
if (![contactID isEqualToString:_contact.contactID])
{
// Ignore
return;
}
}
[self refreshContactDetails];
}
- (void)refreshContactDetails
{
// Check whether the view is loaded
if (!self.isViewLoaded)
{
return;
}
[self refreshContactDisplayName];
[self refreshContactPresence];
[self refreshContactThumbnail];
[self.tableView reloadData];
}
- (NSString*)firstMatrixId
{
NSString* matrixId = nil;
if (_contact.matrixIdentifiers.count > 0)
{
matrixId = _contact.matrixIdentifiers.firstObject;
}
return matrixId;
}
- (void)refreshContactThumbnail
{
UIImage* image = [_contact thumbnailWithPreferedSize:self.contactAvatar.frame.size];
if (!image)
{
NSString* matrixId = self.firstMatrixId;
if (matrixId)
{
image = [AvatarGenerator generateAvatarForMatrixItem:matrixId withDisplayName:_contact.displayName];
}
else
{
image = [AvatarGenerator generateAvatarForText:_contact.displayName];
}
}
self.contactAvatar.image = image;
[self.contactAvatar.layer setCornerRadius:self.contactAvatar.frame.size.width / 2];
[self.contactAvatar setClipsToBounds:YES];
}
- (void)refreshContactDisplayName
{
self.contactNameLabel.text = _contact.displayName;
}
- (void)refreshContactPresence
{
NSString* presenceText;
NSString* matrixId = self.firstMatrixId;
if (matrixId)
{
MXUser *user = nil;
// Consider here all sessions reported into contact manager
NSArray* mxSessions = [MXKContactManager sharedManager].mxSessions;
for (MXSession *mxSession in mxSessions)
{
user = [mxSession userWithUserId:matrixId];
if (user)
{
break;
}
}
presenceText = [Tools presenceText:user];
}
self.contactStatusLabel.text = presenceText;
}
- (void)onThumbnailUpdate:(NSNotification *)notif
{
// sanity check
if ([notif.object isKindOfClass:[NSString class]])
{
NSString* contactID = notif.object;
if ([contactID isEqualToString:_contact.contactID])
{
[self refreshContactThumbnail];
}
}
}
#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];
}
// Main Navigation bar opacity must follow
self.navigationController.navigationBar.translucent = isHidden;
mainNavigationController.navigationBar.translucent = isHidden;
}
#pragma mark - TableView data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger sectionCount = 0;
[actionsArray removeAllObjects];
[directChatsArray removeAllObjects];
actionsIndex = directChatsIndex = -1;
if (!self.mainSession)
{
return 0;
}
NSString *matrixId = self.firstMatrixId;
if (matrixId.length)
{
// Check whether the contact is not the user himself
if (![matrixId isEqualToString:self.mainSession.myUser.userId])
{
if (self.enableVoipCall)
{
// Offer voip call options
[actionsArray addObject:@(ContactDetailsActionStartVoiceCall)];
[actionsArray addObject:@(ContactDetailsActionStartVideoCall)];
}
// Check whether the option Ignore may be presented
if (![self.mainSession isUserIgnored:matrixId])
{
[actionsArray addObject:@(ContactDetailsActionIgnore)];
}
else
{
[actionsArray addObject:@(ContactDetailsActionUnignore)];
}
actionsIndex = sectionCount++;
}
directChatsIndex = sectionCount++;
}
// Else check whether the contact has been instantiated with an email or a matrix id
else if ((!_contact.isMatrixContact && _contact.emailAddresses.count) || [MXTools isEmailAddress:_contact.displayName])
{
directChatsIndex = sectionCount++;
}
else if ([MXTools isMatrixUserIdentifier:_contact.displayName])
{
matrixId = _contact.displayName;
directChatsIndex = sectionCount++;
}
if (matrixId.length)
{
// Retrieve the existing direct chats
NSArray *directRoomIds = self.mainSession.directRooms[matrixId];
// Check whether the room is still existing
for (NSString* directRoomId in directRoomIds)
{
if ([self.mainSession roomWithRoomId:directRoomId])
{
[directChatsArray addObject:directRoomId];
}
}
}
return sectionCount;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == actionsIndex)
{
return actionsArray.count;
}
else if (section == directChatsIndex)
{
return (directChatsArray.count + 1);
}
return 0;
}
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if (section == directChatsIndex)
{
return [VectorL10n roomParticipantsActionSectionDirectChats];
}
return nil;
}
- (NSString*)actionButtonTitle:(ContactDetailsAction)action
{
NSString *title;
switch (action)
{
case ContactDetailsActionIgnore:
title = [VectorL10n roomParticipantsActionIgnore];
break;
case ContactDetailsActionUnignore:
title = [VectorL10n roomParticipantsActionUnignore];
break;
case ContactDetailsActionStartChat:
title = [VectorL10n roomParticipantsActionStartNewChat];
break;
case ContactDetailsActionStartVoiceCall:
title = [VectorL10n roomParticipantsActionStartVoiceCall];
break;
case ContactDetailsActionStartVideoCall:
title = [VectorL10n roomParticipantsActionStartVideoCall];
break;
default:
break;
}
return title;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
if (indexPath.section == actionsIndex)
{
TableViewCellWithButton *cellWithButton = [tableView dequeueReusableCellWithIdentifier:[TableViewCellWithButton defaultReuseIdentifier] forIndexPath:indexPath];
if (indexPath.row < actionsArray.count)
{
NSNumber *actionNumber = actionsArray[indexPath.row];
NSString *title = [self actionButtonTitle:actionNumber.unsignedIntegerValue];
[cellWithButton.mxkButton setTitle:title forState:UIControlStateNormal];
[cellWithButton.mxkButton setTitle:title forState:UIControlStateHighlighted];
[cellWithButton.mxkButton setTitleColor:ThemeService.shared.theme.textPrimaryColor forState:UIControlStateNormal];
[cellWithButton.mxkButton setTitleColor:ThemeService.shared.theme.textPrimaryColor 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];
}
}
else
{
roomCell.avatarImageView.image = AssetImages.startChat.image;
roomCell.avatarImageView.defaultBackgroundColor = [UIColor clearColor];
roomCell.titleLabel.text = [VectorL10n roomParticipantsActionStartNewChat];
}
cell = roomCell;
}
else
{
// Create a fake cell to prevent app from crashing
cell = [[UITableViewCell alloc] init];
}
return cell;
}
#pragma mark - TableView 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 heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == directChatsIndex)
{
return [RoomTableViewCell cellHeight];
}
return TABLEVIEW_ROW_CELL_HEIGHT;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section == actionsIndex)
{
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
Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerSearchContactDetail;
[[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 = ContactDetailsActionStartChat;
[self onActionButtonPressed:button];
}
}
else
{
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if (selectedCell && [selectedCell isKindOfClass:TableViewCellWithButton.class])
{
TableViewCellWithButton *cell = (TableViewCellWithButton*)selectedCell;
[self onActionButtonPressed:cell.mxkButton];
}
}
}
#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];
}
}
#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 ContactDetailsActionIgnore:
{
// Prompt user to ignore content from this user
__weak __typeof(self) weakSelf = self;
[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) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
// Add the user to the blacklist: ignored users
[self addPendingActionMask];
[self.mainSession ignoreUsers:@[self.firstMatrixId]
success:^{
[self removePendingActionMask];
} failure:^(NSError *error) {
[self removePendingActionMask];
MXLogDebug(@"[ContactDetailsViewController] Ignore %@ failed", self.firstMatrixId);
// Notify MatrixKit user
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
}
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n no]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
}
}]];
[currentAlert mxk_setAccessibilityIdentifier:@"ContactDetailsVCIgnoreAlert"];
[self presentViewController:currentAlert animated:YES completion:nil];
break;
}
case ContactDetailsActionUnignore:
{
// Remove the member from the ignored user list.
[self addPendingActionMask];
__weak __typeof(self) weakSelf = self;
[self.mainSession unIgnoreUsers:@[self.firstMatrixId]
success:^{
__strong __typeof(weakSelf)self = weakSelf;
[self removePendingActionMask];
} failure:^(NSError *error) {
__strong __typeof(weakSelf)self = weakSelf;
[self removePendingActionMask];
MXLogDebug(@"[ContactDetailsViewController] Unignore %@ failed", self.firstMatrixId);
// Notify MatrixKit user
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
break;
}
case ContactDetailsActionStartChat:
{
[self addPendingActionMask];
if (_contact.matrixIdentifiers.count)
{
[[AppDelegate theDelegate] showNewDirectChat:self.firstMatrixId withMatrixSession:self.mainSession completion:^{
[self removePendingActionMask];
}];
}
else
{
// Prepare the invited participant data
NSArray *inviteArray;
NSArray *invite3PIDArray;
NSString *participantId;
if (_contact.emailAddresses.count)
{
// This is a local contact, consider the first email by default.
// TODO: Prompt the user to select the right email.
MXKEmail *email = _contact.emailAddresses.firstObject;
participantId = email.emailAddress;
}
else
{
// This is the text filled by the user.
participantId = _contact.displayName;
}
// Is it an email or a Matrix user ID?
if ([MXTools isEmailAddress:participantId])
{
// The identity server must be defined
if (!self.mainSession.matrixRestClient.identityServer)
{
[self removePendingActionMask];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[VectorL10n error]
message:[VectorL10n roomParticipantsStartNewChatErrorUsingUserEmailWithoutIdentityServer]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
return;
}
// The hostname of the identity server must not have the protocol part
NSString *identityServer = self.mainSession.matrixRestClient.identityServer;
if ([identityServer hasPrefix:@"http://"] || [identityServer hasPrefix:@"https://"])
{
identityServer = [identityServer substringFromIndex:[identityServer rangeOfString:@"://"].location + 3];
}
MXInvite3PID *invite3PID = [[MXInvite3PID alloc] init];
invite3PID.identityServer = identityServer;
invite3PID.medium = kMX3PIDMediumEmail;
invite3PID.address = participantId;
invite3PIDArray = @[invite3PID];
}
else //if ([MXTools isMatrixUserIdentifier:participantId])
{
inviteArray = @[participantId];
}
MXWeakify(self);
void (^onFailure)(NSError *) = ^(NSError *error){
MXStrongifyAndReturnIfNil(self);
MXLogDebug(@"[ContactDetailsViewController] Create room failed");
self->roomCreationRequest = nil;
[self removePendingActionMask];
// Notify user
[[AppDelegate theDelegate] showErrorAsAlert:error];
};
// Create a new room
[self.mainSession vc_canEnableE2EByDefaultInNewRoomWithUsers:inviteArray success:^(BOOL canEnableE2E) {
MXStrongifyAndReturnIfNil(self);
MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new];
roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate;
roomCreationParameters.inviteArray = inviteArray;
roomCreationParameters.invite3PIDArray = invite3PIDArray;
roomCreationParameters.isDirect = YES;
roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat;
if (canEnableE2E && roomCreationParameters.invite3PIDArray == nil)
{
roomCreationParameters.initialStateEvents = @[
[MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm
]];
}
self->roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) {
self->roomCreationRequest = nil;
[self removePendingActionMask];
Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerCreated;
[[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession];
} failure:onFailure];
} failure:onFailure];
}
break;
}
case ContactDetailsActionStartVoiceCall:
case ContactDetailsActionStartVideoCall:
{
BOOL isVideoCall = (button.tag == ContactDetailsActionStartVideoCall);
[self addPendingActionMask];
NSString *matrixId = self.firstMatrixId;
MXRoom* directRoom = [self.mainSession directJoinedRoomWithUserId:matrixId];
// 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:matrixId];
roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) {
self->roomCreationRequest = nil;
// 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(@"[ContactDetailsViewController] Create room failed");
self->roomCreationRequest = nil;
[self removePendingActionMask];
// Notify user
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
}
break;
}
default:
break;
}
}
}
- (void)handleTapGesture:(UITapGestureRecognizer*)tapGestureRecognizer
{
UIView *view = tapGestureRecognizer.view;
if (view == self.contactNameLabelMask && _contact.displayName)
{
if ([self.contactNameLabel.text isEqualToString:_contact.displayName])
{
// Display contact's matrix id
NSString *matrixId = self.firstMatrixId;
if (matrixId.length)
{
self.contactNameLabel.text = matrixId;
}
}
else
{
// Restore display name
self.contactNameLabel.text = _contact.displayName;
}
}
else if (view == contactTitleView.memberAvatarMask || view == self.contactAvatarMask)
{
// Show the avatar in full screen
__block MXKImageView * avatarFullScreenView = [[MXKImageView alloc] initWithFrame:CGRectZero];
avatarFullScreenView.stretchable = YES;
MXWeakify(self);
[avatarFullScreenView setRightButtonTitle:[VectorL10n ok] handler:^(MXKImageView* imageView, NSString* buttonTitle) {
MXStrongifyAndReturnIfNil(self);
[avatarFullScreenView dismissSelection];
[avatarFullScreenView removeFromSuperview];
avatarFullScreenView = nil;
self->isStatusBarHidden = NO;
// Trigger status bar update
[self setNeedsStatusBarAppearanceUpdate];
}];
NSString *avatarURL = nil;
if (self.firstMatrixId)
{
MXUser *user = [self.mainSession userWithUserId:self.firstMatrixId];
avatarURL = user.avatarUrl;
}
// TODO: Display the orignal contact avatar when the contast is not a Matrix user
[avatarFullScreenView setImageURI:avatarURL
withType:nil
andImageOrientation:UIImageOrientationUp
previewImage:self.contactAvatar.image
mediaManager:self.mainSession.mediaManager];
[avatarFullScreenView showFullScreen];
isStatusBarHidden = YES;
// Trigger status bar update
[self setNeedsStatusBarAppearanceUpdate];
}
}
#pragma mark - RoomMemberTitleViewDelegate
- (void)roomMemberTitleViewDidLayoutSubview:(RoomMemberTitleView*)titleView
{
[self viewDidLayoutSubviews];
}
@end