element-ios/Vector/ViewController/ContactsTableViewController.m
giomfo 5f34edb8ba Prepare #904: Improve the people invite screens
- Rename `ContactPickerViewController` with `HomePeopleSearchViewController`.
- Define a basic view controller class `ContactsTableViewController` to display/filter a contacts list.
- Make `StartChatViewController` inherit of this new class `ContactsTableViewController` to handle contact invite.
2017-01-10 17:56:30 +01:00

535 lines
17 KiB
Objective-C

/*
Copyright 2017 OpenMarket 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 "ContactsTableViewController.h"
#import "RageShakeManager.h"
#import "AppDelegate.h"
@interface ContactsTableViewController ()
{
// Search processing
dispatch_queue_t searchProcessingQueue;
NSUInteger searchProcessingCount;
NSString *searchProcessingText;
NSMutableArray<MXKContact*> *searchProcessingLocalContacts;
NSMutableArray<MXKContact*> *searchProcessingMatrixContacts;
// Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar.
id kAppDelegateDidTapStatusBarNotificationObserver;
BOOL forceSearchResultRefresh;
}
@end
@implementation ContactsTableViewController
#pragma mark -
- (void)finalizeInit
{
[super finalizeInit];
// Setup `MXKViewControllerHandling` properties
self.defaultBarTintColor = kVectorNavBarTintColor;
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
// Prepare search session
searchProcessingQueue = dispatch_queue_create("StartChatViewController", DISPATCH_QUEUE_SERIAL);
searchProcessingCount = 0;
searchProcessingText = nil;
searchProcessingLocalContacts = nil;
searchProcessingMatrixContacts = nil;
ignoredContactsByEmail = [NSMutableDictionary dictionary];
ignoredContactsByMatrixId = [NSMutableDictionary dictionary];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Add each matrix session by default.
NSArray *sessions = [AppDelegate theDelegate].mxSessions;
for (MXSession *mxSession in sessions)
{
[self addMatrixSession:mxSession];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)destroy
{
filteredLocalContacts = nil;
filteredMatrixContacts = nil;
ignoredContactsByEmail = nil;
ignoredContactsByMatrixId = nil;
userContact = nil;
if (currentAlert)
{
[currentAlert dismiss:NO];
currentAlert = nil;
}
searchProcessingQueue = nil;
searchProcessingLocalContacts = nil;
searchProcessingMatrixContacts = nil;
[super destroy];
}
- (void)addMatrixSession:(MXSession *)mxSession
{
[super addMatrixSession:mxSession];
// FIXME: Handle multi accounts
NSString *displayName = NSLocalizedStringFromTable(@"you", @"Vector", nil);
userContact = [[MXKContact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:self.mainSession.myUser.userId];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Observe kAppDelegateDidTapStatusBarNotification.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:YES];
}];
// Register on contact update notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactManagerDidUpdate:) name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactManagerDidUpdate:) name:kMXKContactManagerDidUpdateLocalContactsNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactManagerDidUpdate:) name:kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (currentAlert)
{
[currentAlert dismiss:NO];
currentAlert = nil;
}
if (kAppDelegateDidTapStatusBarNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver];
kAppDelegateDidTapStatusBarNotificationObserver = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateLocalContactsNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification object:nil];
}
#pragma mark -
- (void)searchWithPattern:(NSString *)searchText forceRefresh:(BOOL)forceRefresh
{
// Update search results.
searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
searchProcessingCount++;
[self startActivityIndicator];
dispatch_async(searchProcessingQueue, ^{
if (!searchText.length)
{
searchProcessingLocalContacts = nil;
searchProcessingMatrixContacts = nil;
}
else if (forceRefresh || !searchProcessingText.length || [searchText hasPrefix:searchProcessingText] == NO)
{
// Retrieve all the local contacts
searchProcessingMatrixContacts = [self unfilteredLocalContactsArray];
// Retrieve all known matrix users
searchProcessingMatrixContacts = [self unfilteredMatrixContactsArray];
}
for (NSUInteger index = 0; index < searchProcessingLocalContacts.count;)
{
MXKContact* contact = searchProcessingLocalContacts[index];
if (![contact hasPrefix:searchText])
{
[searchProcessingLocalContacts removeObjectAtIndex:index];
}
else
{
// Next
index++;
}
}
for (NSUInteger index = 0; index < searchProcessingMatrixContacts.count;)
{
MXKContact* contact = searchProcessingMatrixContacts[index];
if (![contact hasPrefix:searchText])
{
[searchProcessingMatrixContacts removeObjectAtIndex:index];
}
else
{
// Next
index++;
}
}
// Sort the refreshed list of the invitable contacts
[[MXKContactManager sharedManager] sortAlphabeticallyContacts:searchProcessingLocalContacts];
[[MXKContactManager sharedManager] sortContactsByLastActiveInformation:searchProcessingMatrixContacts];
searchProcessingText = searchText;
dispatch_sync(dispatch_get_main_queue(), ^{
// Render the search result only if there is no other search in progress.
searchProcessingCount --;
if (!searchProcessingCount)
{
if (!forceSearchResultRefresh)
{
[self stopActivityIndicator];
// Update the filtered contacts.
currentSearchText = searchProcessingText;
filteredLocalContacts = searchProcessingLocalContacts;
filteredMatrixContacts = searchProcessingMatrixContacts;
// Refresh display
[self refreshTableView];
// Force scroll to top
[self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:YES];
}
else
{
// Launch a new search
forceSearchResultRefresh = NO;
[self searchWithPattern:searchProcessingText forceRefresh:YES];
}
}
});
});
}
- (void)refreshTableView
{
[self.tableView reloadData];
}
#pragma mark - Internals
- (void)onContactManagerDidUpdate:(NSNotification *)notif
{
// Check whether a search is in progress
if (searchProcessingCount)
{
forceSearchResultRefresh = YES;
return;
}
// Refresh the search result
[self searchWithPattern:currentSearchText forceRefresh:YES];
}
- (NSMutableArray<MXKContact*>*)unfilteredLocalContactsArray
{
// Retrieve all the contacts obtained by splitting each local contact by contact method. This list is ordered alphabetically.
NSMutableArray *unfilteredLocalContacts = [NSMutableArray arrayWithArray:[MXKContactManager sharedManager].localContactsSplitByContactMethod];
// Remove the ignored contacts
for (NSUInteger index = 0; index < unfilteredLocalContacts.count;)
{
MXKContact* contact = unfilteredLocalContacts[index];
NSArray *identifiers = contact.matrixIdentifiers;
if (identifiers.count)
{
if ([ignoredContactsByMatrixId objectForKey:identifiers.firstObject])
{
[unfilteredLocalContacts removeObjectAtIndex:index];
continue;
}
}
else
{
NSArray *emails = contact.emailAddresses;
if (emails.count)
{
MXKEmail *email = emails.firstObject;
if ([ignoredContactsByEmail objectForKey:email.emailAddress])
{
[unfilteredLocalContacts removeObjectAtIndex:index];
continue;
}
}
}
index++;
}
return unfilteredLocalContacts;
}
- (NSMutableArray<MXKContact*>*)unfilteredMatrixContactsArray
{
NSArray *matrixContacts = [MXKContactManager sharedManager].matrixContacts;
NSMutableArray *unfilteredMatrixContacts = [NSMutableArray arrayWithCapacity:matrixContacts.count];
// Matrix ids: split contacts with several ids, and remove the current participants.
for (MXKContact* contact in matrixContacts)
{
NSArray *identifiers = contact.matrixIdentifiers;
if (identifiers.count > 1)
{
for (NSString *userId in identifiers)
{
if ([ignoredContactsByMatrixId objectForKey:userId] == nil)
{
MXKContact *splitContact = [[MXKContact alloc] initMatrixContactWithDisplayName:contact.displayName andMatrixID:userId];
[unfilteredMatrixContacts addObject:splitContact];
}
}
}
else if (identifiers.count)
{
NSString *userId = identifiers.firstObject;
if ([ignoredContactsByMatrixId objectForKey:userId] == nil)
{
[unfilteredMatrixContacts addObject:contact];
}
}
}
return unfilteredMatrixContacts;
}
#pragma mark - UITableView data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger count = 0;
searchInputSection = filteredLocalContactsSection = filteredMatrixContactsSection = -1;
if (currentSearchText.length)
{
searchInputSection = count++;
if (filteredLocalContacts.count)
{
filteredLocalContactsSection = count++;
}
if (filteredMatrixContacts.count)
{
filteredMatrixContactsSection = count++;
}
}
else
{
// Display by default the full address book ordered alphabetically, mixing Matrix enabled and non-Matrix enabled users.
if (!filteredLocalContacts)
{
filteredLocalContacts = [self unfilteredLocalContactsArray];
}
if (filteredLocalContacts.count)
{
filteredLocalContactsSection = count++;
}
}
return count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger count = 0;
if (section == searchInputSection)
{
count = 1;
}
else if (section == filteredLocalContactsSection)
{
count = filteredLocalContacts.count;
}
else if (section == filteredMatrixContactsSection)
{
count = filteredMatrixContacts.count;
}
return count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ContactTableViewCell* contactCell = [tableView dequeueReusableCellWithIdentifier:[ContactTableViewCell defaultReuseIdentifier]];
if (!contactCell)
{
contactCell = [[ContactTableViewCell alloc] init];
}
else
{
// Restore default values
contactCell.accessoryView = nil;
contactCell.contentView.alpha = 1;
contactCell.userInteractionEnabled = YES;
}
MXKContact *contact;
if (indexPath.section == searchInputSection)
{
// Show what the user is typing in a cell. So that he can click on it
contact = [[MXKContact alloc] initMatrixContactWithDisplayName:currentSearchText andMatrixID:nil];
contactCell.selectionStyle = UITableViewCellSelectionStyleDefault;
}
else if (indexPath.section == filteredLocalContactsSection)
{
if (indexPath.row < filteredLocalContacts.count)
{
contact = filteredLocalContacts[indexPath.row];
contactCell.selectionStyle = UITableViewCellSelectionStyleDefault;
contactCell.showMatrixIdInDisplayName = YES;
}
}
else if (indexPath.section == filteredMatrixContactsSection)
{
if (indexPath.row < filteredMatrixContacts.count)
{
contact = filteredMatrixContacts[indexPath.row];
contactCell.selectionStyle = UITableViewCellSelectionStyleDefault;
contactCell.showMatrixIdInDisplayName = YES;
}
}
if (contact)
{
[contactCell render:contact];
// The search displays contacts to invite. Add a plus icon to the cell
// in order to make it more understandable for the end user
if (indexPath.section == filteredLocalContactsSection || indexPath.section == filteredMatrixContactsSection)
{
contactCell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"plus_icon"]];
}
else if (indexPath.section == searchInputSection)
{
// This is the text entered by the user
// Check whether the search input is a valid email or a Matrix user ID before adding the plus icon.
if (![MXTools isEmailAddress:currentSearchText] && ![MXTools isMatrixUserIdentifier:currentSearchText])
{
contactCell.contentView.alpha = 0.5;
contactCell.userInteractionEnabled = NO;
}
else
{
contactCell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"plus_icon"]];
}
}
}
return contactCell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}
#pragma mark - UITableView delegate
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section == filteredLocalContactsSection || section == filteredMatrixContactsSection)
{
return 30.0;
}
return 0;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView* sectionHeader;
if (section == filteredLocalContactsSection || section == filteredMatrixContactsSection)
{
sectionHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 30)];
sectionHeader.backgroundColor = kVectorColorLightGrey;
CGRect frame = sectionHeader.frame;
frame.origin.x = 20;
frame.origin.y = 5;
frame.size.width = sectionHeader.frame.size.width - 10;
frame.size.height -= 10;
UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame];
headerLabel.font = [UIFont boldSystemFontOfSize:15.0];
headerLabel.backgroundColor = [UIColor clearColor];
if (section == filteredLocalContactsSection)
{
headerLabel.text = NSLocalizedStringFromTable(@"contacts_address_book_section", @"Vector", nil);
}
else if (section == filteredMatrixContactsSection)
{
headerLabel.text = NSLocalizedStringFromTable(@"contacts_matrix_users_section", @"Vector", nil);
}
[sectionHeader addSubview:headerLabel];
}
return sectionHeader;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 74.0;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do nothing by default - `ContactsTableViewController-inherited` instance must override this method.
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
@end