// // ContactsTableViewController.m // Signal // // Created by Dylan Bourgeois on 29/10/14. // Copyright (c) 2014 Open Whisper Systems. All rights reserved. // #import "ContactsTableViewController.h" #import "ContactDetailTableViewController.h" #import "ContactTableViewCell.h" #import "Environment.h" #import "Contact.h" #import "ContactsManager.h" #import "LocalizableText.h" #import "PhoneNumberDirectoryFilterManager.h" #import "NotificationManifest.h" #import #define REFRESH_TIMEOUT 20 static NSString *const CONTACT_BROWSE_TABLE_CELL_IDENTIFIER = @"ContactTableViewCell"; @interface ContactsTableViewController () { NSDictionary *latestAlphabeticalContacts; NSArray *searchResults; } @property (nonatomic, strong) UILabel *emptyViewLabel; @property NSArray *latestSortedAlphabeticalContactKeys; @property NSArray *latestContacts; @property (nonatomic, strong) UISearchController *searchController; @end @implementation ContactsTableViewController - (void)viewDidLoad { [super viewDidLoad]; [self.navigationController.navigationBar setTranslucent:NO]; [self initializeObservers]; [self initializeRefreshControl]; [self initializeTableView]; [self initializeSearch]; [self setupContacts]; [self.tableView reloadData]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - Initializers -(void)initializeSearch { self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil]; self.searchController.searchResultsUpdater = self; self.searchController.searchBar.frame = CGRectMake(self.searchController.searchBar.frame.origin.x, self.searchController.searchBar.frame.origin.y, self.searchController.searchBar.frame.size.width, 44.0); self.tableView.tableHeaderView = self.searchController.searchBar; self.searchController.dimsBackgroundDuringPresentation = NO; self.searchController.hidesNavigationBarDuringPresentation = NO; self.definesPresentationContext = YES; self.searchController.searchBar.searchBarStyle = UISearchBarStyleMinimal; } -(void)initializeRefreshControl { UIRefreshControl *refreshControl = [[UIRefreshControl alloc]init]; [refreshControl addTarget:self action:@selector(refreshContacts) forControlEvents:UIControlEventValueChanged]; self.refreshControl = refreshControl; [self.contactTableView addSubview:self.refreshControl]; } -(void)initializeTableView { self.tableView.contentOffset = CGPointMake(0, 44); self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; } -(void)initializeObservers { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contactsDidRefresh) name:NOTIFICATION_DIRECTORY_WAS_UPDATED object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contactRefreshFailed) name:NOTIFICATION_DIRECTORY_FAILED object:nil]; } #pragma mark - UISearchResultsUpdating -(void)updateSearchResultsForSearchController:(UISearchController *)searchController { NSString *searchString = [self.searchController.searchBar text]; [self filterContentForSearchText:searchString scope:nil]; [self.tableView reloadData]; } #pragma mark - UISearchBarDelegate - (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope { [self updateSearchResultsForSearchController:self.searchController]; } #pragma mark - Filter - (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope { NSPredicate *numberPredicate = [NSPredicate predicateWithFormat:@"ANY SELF.userTextPhoneNumbers contains[cd] %@ OR fullName contains[c] %@", searchText, searchText]; searchResults = [self.latestContacts filteredArrayUsingPredicate:numberPredicate]; if (!searchResults.count && _searchController.searchBar.text.length == 0) { searchResults = self.latestContacts; } } #pragma mark - Contact functions - (void)setupContacts { ObservableValue *observableContacts = Environment.getCurrent.contactsManager.getObservableRedPhoneUsers; [observableContacts watchLatestValue:^(NSArray *latestContacts) { _latestContacts = latestContacts; [self onSearchOrContactChange:nil]; } onThread:NSThread.mainThread untilCancelled:nil]; } - (NSArray *)contactsForSectionIndex:(NSUInteger)index { return [latestAlphabeticalContacts valueForKey:self.latestSortedAlphabeticalContactKeys[index]]; } -(NSMutableDictionary*)alphabetDictionaryInit { NSDictionary * dic; dic = @{ @"A": @[], @"B": @[], @"C": @[], @"D": @[], @"E": @[], @"F": @[], @"G": @[], @"H": @[], @"I": @[], @"J": @[], @"K": @[], @"L": @[], @"M": @[], @"N": @[], @"O": @[], @"P": @[], @"Q": @[], @"R": @[], @"S": @[], @"T": @[], @"U": @[], @"V": @[], @"W": @[], @"X": @[], @"Y": @[], @"Z": @[] }; return [dic mutableCopy]; } #pragma mark - Table view data source - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (self.searchController.active) { return (NSInteger)[searchResults count]; } else { return (NSInteger)[[self contactsForSectionIndex:(NSUInteger)section] count]; } } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { if ([[self contactsForSectionIndex:(NSUInteger)section] count] && !self.searchController.isActive) { return self.latestSortedAlphabeticalContactKeys[(NSUInteger)section]; } else { return nil; } } - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section { UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view; [header.textLabel setTextColor:[UIColor ows_blackColor]]; [header.textLabel setFont:[UIFont ows_thinFontWithSize:14.0f]]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { if (self.searchController.active) { return 1; } else { return (NSInteger)[[latestAlphabeticalContacts allKeys] count]; } } - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { tableView.sectionIndexBackgroundColor = [UIColor clearColor]; return _latestSortedAlphabeticalContactKeys; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { ContactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CONTACT_BROWSE_TABLE_CELL_IDENTIFIER]; if (!cell) { cell = [[ContactTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CONTACT_BROWSE_TABLE_CELL_IDENTIFIER]; } [cell configureWithContact:[self contactForIndexPath:indexPath]]; return cell; } -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [self performSegueWithIdentifier:@"DetailSegue" sender:self]; [tableView deselectRowAtIndexPath:indexPath animated:YES]; } -(Contact*)contactForIndexPath:(NSIndexPath*)indexPath { Contact *contact = nil; if (self.searchController.active) { contact = [searchResults objectAtIndex:(NSUInteger)indexPath.row]; } else { NSArray *contactSection = [self contactsForSectionIndex:(NSUInteger)indexPath.section]; contact = contactSection[(NSUInteger)indexPath.row]; } return contact; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 55.0f; } #pragma mark - Segue -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"DetailSegue"]) { Contact *contact = nil; ContactDetailTableViewController * detailvc = [segue destinationViewController]; NSIndexPath * indexPath = [self.tableView indexPathForSelectedRow]; if (self.searchController.active) { contact = [searchResults objectAtIndex:(NSUInteger)indexPath.row]; } else { NSArray *contactSection = [self contactsForSectionIndex:(NSUInteger)indexPath.section]; contact = contactSection[(NSUInteger)indexPath.row]; } detailvc.contact = contact; } } #pragma mark - Refresh controls - (void)onSearchOrContactChange:(NSString *)searchTerm { if (_latestContacts) { latestAlphabeticalContacts = [ContactsManager groupContactsByFirstLetter:_latestContacts matchingSearchString:searchTerm]; NSArray *contactKeys = [latestAlphabeticalContacts allKeys]; _latestSortedAlphabeticalContactKeys = [contactKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; [_contactTableView reloadData]; } } - (void)refreshContacts{ Environment *env = [Environment getCurrent]; PhoneNumberDirectoryFilterManager *manager = [env phoneDirectoryManager]; [manager forceUpdate]; } - (void)contactRefreshFailed{ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:TIMEOUT message:TIMEOUT_CONTACTS_DETAIL delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil]; [alert show]; [self.refreshControl endRefreshing]; } - (void)contactsDidRefresh{ if (_latestContacts.count == 0) { CGRect r = CGRectMake(0, 60, 300, 70); _emptyViewLabel = [[UILabel alloc]initWithFrame:r]; _emptyViewLabel.text = @"None of your contacts are Signal users yet."; _emptyViewLabel.textColor = [UIColor ows_darkGrayColor]; _emptyViewLabel.font = [UIFont ows_thinFontWithSize:14.0f]; _emptyViewLabel.textAlignment = NSTextAlignmentCenter; self.tableView.tableFooterView = _emptyViewLabel; } else { self.tableView.tableFooterView = self.tableView.tableFooterView = [[UIView alloc]initWithFrame:CGRectZero]; [self.tableView reloadData]; } [self.refreshControl endRefreshing]; } @end