ContactsListTableView.m 18.2 KB
Newer Older
DanmeiChen's avatar
DanmeiChen committed
1 2
/*
 * Copyright (c) 2010-2019 Belledonne Communications SARL.
3
 *
DanmeiChen's avatar
DanmeiChen committed
4
 * This file is part of linphone-iphone
5
 *
DanmeiChen's avatar
DanmeiChen committed
6 7 8 9
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
10
 *
DanmeiChen's avatar
DanmeiChen committed
11 12 13 14
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
15
 *
DanmeiChen's avatar
DanmeiChen committed
16 17
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18
 */
19

Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
20
#import "ContactsListTableView.h"
21
#import "UIContactCell.h"
Yann Diorcet's avatar
Yann Diorcet committed
22
#import "LinphoneManager.h"
Yann Diorcet's avatar
Yann Diorcet committed
23
#import "PhoneMainView.h"
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
24
#import "Utils.h"
25

Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
26
@implementation ContactsListTableView
27
NSArray *sortedAddresses;
28

Yann Diorcet's avatar
Yann Diorcet committed
29 30 31
#pragma mark - Lifecycle Functions

- (void)initContactsTableViewController {
32
	addressBookMap = [[OrderedDictionary alloc] init];
33
	sortedAddresses = [[NSArray alloc] init];
34 35 36 37 38 39 40 41 42 43
        [NSNotificationCenter.defaultCenter
            addObserver:self
               selector:@selector(onAddressBookUpdate:)
                   name:kLinphoneAddressBookUpdate
                 object:nil];
        [[NSNotificationCenter defaultCenter]
            addObserver:self
               selector:@selector(onAddressBookUpdate:)
                   name:CNContactStoreDidChangeNotification
                 object:nil];
44
}
45

46
- (void)onAddressBookUpdate:(NSNotification *)k {
47
	if (!_ongoing && ((PhoneMainView.instance.currentView == ContactsListView.compositeViewDescription) || (IPAD && PhoneMainView.instance.currentView == ContactDetailsView.compositeViewDescription))) {
48 49
		[self loadData];
	}
Yann Diorcet's avatar
Yann Diorcet committed
50 51
}

52 53 54 55 56 57 58 59 60 61
- (void)viewWillAppear:(BOOL)animated {
	[super viewWillAppear:animated];
	if (IPAD) {
		if (![self selectFirstRow]) {
			ContactDetailsView *view = VIEW(ContactDetailsView);
			[view setContact:nil];
		}
	}
}

Yann Diorcet's avatar
Yann Diorcet committed
62
- (id)init {
63 64
	self = [super init];
	if (self) {
Yann Diorcet's avatar
Yann Diorcet committed
65
		[self initContactsTableViewController];
66
	}
67
	_ongoing = FALSE;
68
	return self;
Yann Diorcet's avatar
Yann Diorcet committed
69 70 71
}

- (id)initWithCoder:(NSCoder *)decoder {
72 73
	self = [super initWithCoder:decoder];
	if (self) {
Yann Diorcet's avatar
Yann Diorcet committed
74 75
		[self initContactsTableViewController];
	}
76 77
	return self;
}
Yann Diorcet's avatar
Yann Diorcet committed
78 79

- (void)dealloc {
80
	[[NSNotificationCenter defaultCenter] removeObserver:self];
81 82 83 84 85 86 87 88 89
	[self removeAllContacts];
}

- (void)removeAllContacts {
	for (NSInteger j = 0; j < [self.tableView numberOfSections]; ++j) {
		for (NSInteger i = 0; i < [self.tableView numberOfRowsInSection:j]; ++i) {
			[[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]] setContact:nil];
		}
	}
Yann Diorcet's avatar
Yann Diorcet committed
90 91
}

92 93
#pragma mark -

94 95
static int ms_strcmpfuz(const char *fuzzy_word, const char *sentence) {
	if (!fuzzy_word || !sentence) {
96 97
		return fuzzy_word == sentence;
	}
98 99
	const char *c = fuzzy_word;
	const char *within_sentence = sentence;
100 101 102 103 104 105 106 107 108 109 110
	for (; c != NULL && *c != '\0' && within_sentence != NULL; ++c) {
		within_sentence = strchr(within_sentence, *c);
		// Could not find c character in sentence. Abort.
		if (within_sentence == NULL) {
			break;
		}
		// since strchr returns the index of the matched char, move forward
		within_sentence++;
	}

	// If the whole fuzzy was found, returns 0. Otherwise returns number of characters left.
111
	return (int)(within_sentence != NULL ? 0 : fuzzy_word + strlen(fuzzy_word) - c);
112
}
Yann Diorcet's avatar
Yann Diorcet committed
113

114
- (NSString *)displayNameForContact:(Contact *)person {
115
	NSString *name = person.displayName;
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
116
	if (name != nil && [name length] > 0 && ![name isEqualToString:NSLocalizedString(@"Unknown", nil)]) {
117 118 119 120 121 122 123
		// Add the contact only if it fuzzy match filter too (if any)
		if ([ContactSelection getNameOrEmailFilter] == nil ||
			(ms_strcmpfuz([[[ContactSelection getNameOrEmailFilter] lowercaseString] UTF8String],
						  [[name lowercaseString] UTF8String]) == 0)) {

			// Sort contacts by first letter. We need to translate the name to ASCII first, because of UTF-8
			// issues. For instance expected order would be:  Alberta(A tilde) before ASylvano.
124
			NSData *name2ASCIIdata = [name dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
125
			NSString *name2ASCII = [[NSString alloc] initWithData:name2ASCIIdata encoding:NSASCIIStringEncoding];
126
			return name2ASCII;
127 128 129 130 131
		}
	}
	return nil;
}

132
- (void)loadData {
133
	_ongoing = TRUE;
134
	LOGI(@"====>>>> Load contact list - Start");
135 136 137
	NSString* previous = [PhoneMainView.instance  getPreviousViewName];
	addressBookMap = [LinphoneManager.instance getLinphoneManagerAddressBookMap];
	BOOL updated = [LinphoneManager.instance getContactsUpdated];
138
	if(([previous isEqualToString:@"ContactsDetailsView"] && updated) || updated || [addressBookMap count] == 0){
Brieuc's avatar
Brieuc committed
139 140
		[LinphoneManager.instance setContactsUpdated:FALSE];
		@synchronized(addressBookMap) {
141 142 143 144 145 146 147 148 149 150 151 152
			NSDictionary *allContacts = [[NSMutableDictionary alloc] initWithDictionary:LinphoneManager.instance.fastAddressBook.addressBookMap];
			sortedAddresses = [[LinphoneManager.instance.fastAddressBook.addressBookMap allKeys] sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
				Contact* first =  [allContacts objectForKey:a];
				Contact* second =  [allContacts objectForKey:b];
				if([[first.firstName lowercaseString] compare:[second.firstName lowercaseString]] == NSOrderedSame)
					return [[first.lastName lowercaseString] compare:[second.lastName lowercaseString]];
				else
					return [[first.firstName lowercaseString] compare:[second.firstName lowercaseString]];
			}];
			
			
			LOGI(@"====>>>> Load contact list - Start 2 !!");
Brieuc's avatar
Brieuc committed
153
			//Set all contacts from ContactCell to nil
154
			for (NSInteger j = 0; j < [self.tableView numberOfSections]; ++j){
Brieuc's avatar
Brieuc committed
155 156 157 158 159 160 161 162
				for (NSInteger i = 0; i < [self.tableView numberOfRowsInSection:j]; ++i)
				{
					[[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]] setContact:nil];
				}
			}
			
			// Reset Address book
			[addressBookMap removeAllObjects];
163
			for (NSString *addr in sortedAddresses) {
Brieuc's avatar
Brieuc committed
164 165 166 167 168 169 170 171 172
				Contact *contact = nil;
				@synchronized(LinphoneManager.instance.fastAddressBook.addressBookMap) {
					contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:addr];
				}
				BOOL add = true;
				// Do not add the contact directly if we set some filter
				if ([ContactSelection getSipFilter] ||
					[ContactSelection emailFilterEnabled]) {
				  add = false;
173 174
				}
				if ([FastAddressBook contactHasValidSipDomain:contact]) {
Brieuc's avatar
Brieuc committed
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
				  add = true;
				}else if (contact.friend &&
					linphone_presence_model_get_basic_status(
						linphone_friend_get_presence_model(
							contact.friend)) ==
						LinphonePresenceBasicStatusOpen) {
				  add = true;
				}

				if (!add && [ContactSelection emailFilterEnabled]) {
				  // Add this contact if it has an email
				  add = (contact.emails.count > 0);
				}

				NSMutableString *name = [self displayNameForContact:contact] ? [[NSMutableString alloc] initWithString: [self displayNameForContact:contact]] : nil;
				if (add && name != nil) {
					NSString *firstChar = [[name substringToIndex:1] uppercaseString];
					// Put in correct subAr
					if ([firstChar characterAtIndex:0] < 'A' || [firstChar characterAtIndex:0] > 'Z') {
						firstChar = @"#";
					}
					NSMutableArray *subAr = [addressBookMap objectForKey:firstChar];
					if (subAr == nil) {
						subAr = [[NSMutableArray alloc] init];
						[addressBookMap insertObject:subAr forKey:firstChar selector:@selector(caseInsensitiveCompare:)];
					}
					NSUInteger idx = [subAr indexOfObject:contact inSortedRange:(NSRange){0, subAr.count} options:NSBinarySearchingInsertionIndex usingComparator:^NSComparisonResult( Contact *_Nonnull obj1, Contact *_Nonnull obj2) {
							return [[self displayNameForContact:obj1] compare:[self displayNameForContact:obj2] options:NSCaseInsensitiveSearch];
					}];
					if (![subAr containsObject:contact]) {
						[subAr insertObject:contact atIndex:idx];
					}
				}
208
			}
Brieuc's avatar
Brieuc committed
209 210 211 212 213 214 215 216 217 218
			// since we refresh the tableview, we must perform this on main
			// thread
			dispatch_async(dispatch_get_main_queue(), ^(void) {
				if (IPAD) {
					if (!([self totalNumberOfItems] > 0)) {
						ContactDetailsView *view = VIEW(ContactDetailsView);
						[view setContact:nil];
					}
				}
			});
219
		}
220 221
		[LinphoneManager.instance setLinphoneManagerAddressBookMap:addressBookMap];
	}
222
	LOGI(@"====>>>> Load contact list - End");
223
	[super loadData];
Brieuc's avatar
Brieuc committed
224
	_ongoing = FALSE;
Yann Diorcet's avatar
Yann Diorcet committed
225 226
}

227
- (void)loadSearchedData {
228
	LOGI(@"Load search contact list");
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
	@synchronized(addressBookMap) {
		//Set all contacts from ContactCell to nil
		for (NSInteger j = 0; j < [self.tableView numberOfSections]; ++j)
		{
			for (NSInteger i = 0; i < [self.tableView numberOfRowsInSection:j]; ++i)
			{
				[[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]] setContact:nil];
			}
		}
		// Reset Address book
		[addressBookMap removeAllObjects];
		NSMutableArray *subAr = [NSMutableArray new];
		NSMutableArray *subArBegin = [NSMutableArray new];
		NSMutableArray *subArContain = [NSMutableArray new];
		[addressBookMap insertObject:subAr forKey:@"" selector:@selector(caseInsensitiveCompare:)];
244
		for (NSString *addr in sortedAddresses) {
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
                  @synchronized(
                      LinphoneManager.instance.fastAddressBook.addressBookMap) {
                    Contact *contact =
                        [LinphoneManager.instance.fastAddressBook.addressBookMap
                            objectForKey:addr];

                    BOOL add = true;
                    // Do not add the contact directly if we set some filter
                    if ([ContactSelection getSipFilter] ||
                        [ContactSelection emailFilterEnabled]) {
                      add = false;
                    }
                    NSString *filter = [ContactSelection getNameOrEmailFilter];
                    if ([FastAddressBook contactHasValidSipDomain:contact]) {
                      add = true;
                    }
                    if (contact.friend &&
                        linphone_presence_model_get_basic_status(
                            linphone_friend_get_presence_model(
                                contact.friend)) ==
                            LinphonePresenceBasicStatusOpen) {
                      add = true;
                    }

                    if (!add && [ContactSelection emailFilterEnabled]) {
                      // Add this contact if it has an email
                      add = (contact.emails.count > 0);
                    }
                    NSInteger idx_begin = -1;
                    NSInteger idx_sort = -1;
                    NSMutableString *name =
                        [self displayNameForContact:contact]
                            ? [[NSMutableString alloc]
                                  initWithString:
                                      [self displayNameForContact:contact]]
                            : nil;
                    if (add && name != nil) {
                      if ([[contact displayName]
                              rangeOfString:filter
                                    options:NSCaseInsensitiveSearch]
                              .location == 0) {
                        if (![subArBegin containsObject:contact]) {
                          idx_begin = idx_begin + 1;
                          [subArBegin insertObject:contact atIndex:idx_begin];
                        }
                      } else if ([[contact displayName]
                                     rangeOfString:filter
                                           options:NSCaseInsensitiveSearch]
                                     .location != NSNotFound) {
                        if (![subArContain containsObject:contact]) {
                          idx_sort = idx_sort + 1;
                          [subArContain insertObject:contact atIndex:idx_sort];
                        }
                      }
                    }
                  }
                }
                [subArBegin
                    sortUsingComparator:^NSComparisonResult(
                        Contact *_Nonnull obj1, Contact *_Nonnull obj2) {
                      return [[self displayNameForContact:obj1]
                          compare:[self displayNameForContact:obj2]
                          options:NSCaseInsensitiveSearch];
                    }];

                [subArContain
                    sortUsingComparator:^NSComparisonResult(
                        Contact *_Nonnull obj1, Contact *_Nonnull obj2) {
                      return [[self displayNameForContact:obj1]
                          compare:[self displayNameForContact:obj2]
                          options:NSCaseInsensitiveSearch];
                    }];

                [subAr addObjectsFromArray:subArBegin];
                [subAr addObjectsFromArray:subArContain];
                [super loadData];

                // since we refresh the tableview, we must perform this on main
                // thread
                dispatch_async(dispatch_get_main_queue(), ^(void) {
                  if (IPAD) {
                    if (!([self totalNumberOfItems] > 0)) {
                      ContactDetailsView *view = VIEW(ContactDetailsView);
                      [view setContact:nil];
                    }
                  }
                });
        }
333 334 335
}


336 337
#pragma mark - UITableViewDataSource Functions

338
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
339
	return [addressBookMap allKeys];
340 341
}

342
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
343
	return [addressBookMap count];
344 345 346
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
347
	return [(OrderedDictionary *)[addressBookMap objectForKey:[addressBookMap keyAtIndex:section]] count];
348 349 350
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
351
	NSString *kCellId = NSStringFromClass(UIContactCell.class);
352 353
	UIContactCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId];
	if (cell == nil) {
354
		cell = [[UIContactCell alloc] initWithIdentifier:kCellId];
355
	}
Erwan Croze's avatar
Erwan Croze committed
356 357
	NSMutableArray *subAr = [addressBookMap objectForKey:[addressBookMap keyAtIndex:[indexPath section]]];
	Contact *contact = subAr[indexPath.row];
358 359

	// Cached avatar
360
	UIImage *image = [FastAddressBook imageForContact:contact];
361
	[cell.avatarImage setImage:image bordered:NO withRoundedRadius:YES];
362
	[cell setContact:contact];
363 364
	[super accessoryForCell:cell atPath:indexPath];

365
	return cell;
366 367
}

368
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
369
	CGRect frame = CGRectMake(0, 0, tableView.frame.size.width, tableView.sectionHeaderHeight);
370
	UIView *tempView = [[UIView alloc] initWithFrame:frame];
371
	tempView.backgroundColor = [UIColor whiteColor];
372

373
	UILabel *tempLabel = [[UILabel alloc] initWithFrame:frame];
374
	tempLabel.backgroundColor = [UIColor clearColor];
375
	tempLabel.textColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"color_A.png"]];
376 377 378
	tempLabel.text = [addressBookMap keyAtIndex:section];
	tempLabel.textAlignment = NSTextAlignmentCenter;
	tempLabel.font = [UIFont boldSystemFontOfSize:17];
379
	tempLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
380 381 382
	[tempView addSubview:tempLabel];

	return tempView;
383 384 385
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
386 387
	[super tableView:tableView didSelectRowAtIndexPath:indexPath];
	if (![self isEditing]) {
Erwan Croze's avatar
Erwan Croze committed
388 389
		NSMutableArray *subAr = [addressBookMap objectForKey:[addressBookMap keyAtIndex:[indexPath section]]];
		Contact *contact = subAr[indexPath.row];
390 391 392

		// Go to Contact details view
		ContactDetailsView *view = VIEW(ContactDetailsView);
393
		[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
394
		if (([ContactSelection getSelectionMode] != ContactSelectionModeEdit) || !([ContactSelection getAddAddress])) {
395
			[view setContact:contact];
396
		} else {
397
			[view editContact:contact address:[ContactSelection getAddAddress]];
398
		}
399
	}
400 401
}

402 403 404 405
- (void)tableView:(UITableView *)tableView
	commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
	 forRowAtIndexPath:(NSIndexPath *)indexPath {
	if (editingStyle == UITableViewCellEditingStyleDelete) {
406
		[NSNotificationCenter.defaultCenter removeObserver:self];
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
		
		NSString *msg = NSLocalizedString(@"Do you want to delete selected contact?\nIt will also be deleted from your phone's address book.", nil);
		[UIConfirmationDialog ShowWithMessage:msg
					cancelMessage:nil
					confirmMessage:nil
					onCancelClick:nil
					onConfirmationClick:^() {
						[tableView beginUpdates];

						NSString *firstChar = [addressBookMap keyAtIndex:[indexPath section]];
						NSMutableArray *subAr = [addressBookMap objectForKey:firstChar];
						Contact *contact = subAr[indexPath.row];
						[subAr removeObjectAtIndex:indexPath.row];
						if (subAr.count == 0) {
							[addressBookMap removeObjectForKey:firstChar];
							[tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
							withRowAnimation:UITableViewRowAnimationFade];
						}
						UIContactCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
						[cell setContact:NULL];
						[[LinphoneManager.instance fastAddressBook] deleteContact:contact];
						[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
						[tableView endUpdates];

						[NSNotificationCenter.defaultCenter	addObserver:self selector:@selector(onAddressBookUpdate:)
											name:kLinphoneAddressBookUpdate
											object:nil];
						[self loadData];
					}];
436
	}
Yann Diorcet's avatar
Yann Diorcet committed
437
}
438 439 440

- (void)removeSelectionUsing:(void (^)(NSIndexPath *))remover {
	[super removeSelectionUsing:^(NSIndexPath *indexPath) {
441 442
	  [NSNotificationCenter.defaultCenter removeObserver:self];

443
	  NSString *firstChar = [addressBookMap keyAtIndex:[indexPath section]];
Erwan Croze's avatar
Erwan Croze committed
444 445
	  NSMutableArray *subAr = [addressBookMap objectForKey:firstChar];
	  Contact *contact = subAr[indexPath.row];
446 447
	  [subAr removeObjectAtIndex:indexPath.row];
	  if (subAr.count == 0) {
448 449
		  [addressBookMap removeObjectForKey:firstChar];
	  }
450 451
	  UIContactCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
	  [cell setContact:NULL];
452
	  [[LinphoneManager.instance fastAddressBook] deleteContact:contact];
453

454
	  [NSNotificationCenter.defaultCenter addObserver:self
455 456 457
                 selector:@selector(onAddressBookUpdate:)
                     name:kLinphoneAddressBookUpdate
                   object:nil];
458 459 460
	}];
}

461
@end