Contact: factorize code to handle either native contacts and/or LinphoneFriends

parent 6a1faf8d
......@@ -35,8 +35,7 @@
[_allContacts enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
NSString *address = (NSString *)key;
ABRecordRef person = (__bridge ABRecordRef)(value);
NSString *name = [FastAddressBook displayNameForContact:person];
NSString *name = [FastAddressBook displayNameForContact:value];
if ((filter.length == 0) || ([name.lowercaseString containsSubstring:filter.lowercaseString]) ||
([address.lowercaseString containsSubstring:filter.lowercaseString])) {
_contacts[address] = name;
......
......@@ -13,12 +13,18 @@
@interface Contact : NSObject
@property(nonatomic, assign) NSString *firstName;
@property(nonatomic, assign) NSString *lastName;
@property(nonatomic, readonly) ABRecordRef person;
@property(nonatomic, readonly) LinphoneFriend *friend;
@property(nonatomic, retain) NSString *firstName;
@property(nonatomic, retain) NSString *lastName;
@property(nonatomic, strong) NSMutableArray *sipAddresses;
@property(nonatomic, strong) NSMutableArray *emails;
@property(nonatomic, strong) NSMutableArray *phoneNumbers;
- (UIImage *)avatar:(BOOL)thumbnail;
- (NSString *)displayName;
- (instancetype)initWithPerson:(ABRecordRef)person;
- (instancetype)initWithFriend:(LinphoneFriend *) friend;
......@@ -33,5 +39,4 @@
- (BOOL)removeSipAddressAtIndex:(NSInteger)index;
- (BOOL)removePhoneNumberAtIndex:(NSInteger)index;
- (BOOL)removeEmailAtIndex:(NSInteger)index;
@end
This diff is collapsed.
......@@ -66,6 +66,8 @@
if (rmed) {
[tableview deleteRowsAtIndexPaths:@[ path ]
withRowAnimation:animated ? UITableViewRowAnimationFade : UITableViewRowAnimationNone];
} else {
LOGW(@"Cannot remove entry at path %@, skipping", path);
}
}
......@@ -83,6 +85,8 @@
NSUInteger count = [self getSectionData:section].count;
[tableview insertRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:count - 1 inSection:section] ]
withRowAnimation:animated ? UITableViewRowAnimationFade : UITableViewRowAnimationNone];
} else {
LOGW(@"Cannot add entry '%@' in section %d, skipping", value, section);
}
}
......
......@@ -26,11 +26,10 @@
#import "ImagePickerView.h"
@interface ContactDetailsView : TPMultiLayoutViewController <UICompositeViewDelegate, ImagePickerDelegate> {
ABAddressBookRef addressBook;
BOOL inhibUpdate;
}
@property(nonatomic, assign, setter=setContact:) ABRecordRef contact;
@property(nonatomic, assign, setter=setContact:) Contact *contact;
@property(nonatomic, strong) IBOutlet ContactDetailsTableView *tableController;
@property(nonatomic, strong) IBOutlet UIToggleButton *editButton;
@property(nonatomic, strong) IBOutlet UIButton *backButton;
......@@ -49,7 +48,7 @@
- (void)newContact;
- (void)newContact:(NSString *)address;
- (void)editContact:(ABRecordRef)contact;
- (void)editContact:(ABRecordRef)contact address:(NSString *)address;
- (void)setContact:(ABRecordRef)contact;
- (void)editContact:(Contact *)contact;
- (void)editContact:(Contact *)contact address:(NSString *)address;
- (void)setContact:(Contact *)contact;
@end
......@@ -22,46 +22,46 @@
@implementation ContactDetailsView
static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info, void *context);
#pragma mark - Lifecycle Functions
- (id)init {
self = [super initWithNibName:NSStringFromClass(self.class) bundle:[NSBundle mainBundle]];
if (self != nil) {
inhibUpdate = FALSE;
addressBook = ABAddressBookCreateWithOptions(nil, nil);
ABAddressBookRegisterExternalChangeCallback(addressBook, sync_address_book, (__bridge void *)(self));
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(onAddressBookUpdate:)
name:kLinphoneAddressBookUpdate
object:nil];
}
return self;
}
- (void)dealloc {
ABAddressBookUnregisterExternalChangeCallback(addressBook, sync_address_book, (__bridge void *)(self));
CFRelease(addressBook);
[NSNotificationCenter.defaultCenter removeObserver:self];
}
#pragma mark -
- (void)onAddressBookUpdate:(NSNotification *)k {
if (!inhibUpdate && ![_tableController isEditing]) {
[self resetData];
}
}
- (void)resetData {
if (self.isEditing) {
[self setEditing:FALSE];
}
if (_contact == NULL) {
ABAddressBookRevert(addressBook);
return;
}
ABRecordID recordID = ABRecordGetRecordID(_contact);
ABAddressBookRevert(addressBook);
_contact = ABAddressBookGetPersonWithRecordID(addressBook, recordID);
if (_contact != NULL) {
LOGI(@"Reset data to contact %p", _contact);
[_avatarImage setImage:[FastAddressBook imageForContact:_contact thumbnail:NO]
bordered:NO
withRoundedRadius:YES];
[_tableController setContact:[[Contact alloc] initWithPerson:_contact]];
[_tableController setContact:_contact];
_emptyLabel.hidden = YES;
} else {
_emptyLabel.hidden = NO;
......@@ -71,13 +71,6 @@ static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info
}
}
static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
ContactDetailsView *controller = (__bridge ContactDetailsView *)context;
if (!controller->inhibUpdate && ![[controller tableController] isEditing]) {
[controller resetData];
}
}
- (void)removeContact {
if (_contact != NULL) {
inhibUpdate = TRUE;
......@@ -94,41 +87,20 @@ static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info
}
// Add contact to book
CFErrorRef error = NULL;
if (ABRecordGetRecordID(_contact) == kABRecordInvalidID) {
ABAddressBookAddRecord(addressBook, _contact, (CFErrorRef *)&error);
if (error != NULL) {
LOGE(@"Add contact %p: Fail(%@)", _contact, [(__bridge NSError *)error localizedDescription]);
} else {
LOGI(@"Add contact %p: Success!", _contact);
}
}
// Save address book
error = NULL;
inhibUpdate = TRUE;
ABAddressBookSave(addressBook, &error);
inhibUpdate = FALSE;
if (error != NULL) {
LOGE(@"Save AddressBook: Fail(%@)", [(__bridge NSError *)error localizedDescription]);
} else {
LOGI(@"Save AddressBook: Success!");
}
[LinphoneManager.instance.fastAddressBook reload];
[LinphoneManager.instance.fastAddressBook saveContact:_contact];
}
- (void)selectContact:(ABRecordRef)acontact andReload:(BOOL)reload {
- (void)selectContact:(Contact *)acontact andReload:(BOOL)reload {
if (self.isEditing) {
[self setEditing:FALSE];
}
ABAddressBookRevert(addressBook);
_contact = acontact;
_emptyLabel.hidden = (_contact != NULL);
[_avatarImage setImage:[FastAddressBook imageForContact:_contact thumbnail:NO] bordered:NO withRoundedRadius:YES];
[ContactDisplay setDisplayNameLabel:_nameLabel forContact:acontact];
[_tableController setContact:[[Contact alloc] initWithPerson:_contact]];
[ContactDisplay setDisplayNameLabel:_nameLabel forContact:_contact];
[_tableController setContact:_contact];
if (reload) {
[self setEditing:TRUE animated:FALSE];
......@@ -157,25 +129,25 @@ static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info
}
- (void)newContact {
[self selectContact:ABPersonCreate() andReload:YES];
[self selectContact:[[Contact alloc] initWithPerson:ABPersonCreate()] andReload:YES];
}
- (void)newContact:(NSString *)address {
[self selectContact:ABPersonCreate() andReload:NO];
[self selectContact:[[Contact alloc] initWithPerson:ABPersonCreate()] andReload:NO];
[self addCurrentContactContactField:address];
}
- (void)editContact:(ABRecordRef)acontact {
[self selectContact:ABAddressBookGetPersonWithRecordID(addressBook, ABRecordGetRecordID(acontact)) andReload:YES];
- (void)editContact:(Contact *)acontact {
[self selectContact:acontact andReload:YES];
}
- (void)editContact:(ABRecordRef)acontact address:(NSString *)address {
[self selectContact:ABAddressBookGetPersonWithRecordID(addressBook, ABRecordGetRecordID(acontact)) andReload:NO];
- (void)editContact:(Contact *)acontact address:(NSString *)address {
[self selectContact:acontact andReload:NO];
[self addCurrentContactContactField:address];
}
- (void)setContact:(ABRecordRef)acontact {
[self selectContact:ABAddressBookGetPersonWithRecordID(addressBook, ABRecordGetRecordID(acontact)) andReload:NO];
- (void)setContact:(Contact *)acontact {
[self selectContact:acontact andReload:NO];
}
#pragma mark - ViewController Functions
......@@ -348,23 +320,7 @@ static UICompositeViewDescription *compositeDescription = nil;
[VIEW(ImagePickerView).popoverController dismissPopoverAnimated:TRUE];
}
FastAddressBook *fab = LinphoneManager.instance.fastAddressBook;
CFErrorRef error = NULL;
if (!ABPersonRemoveImageData(_contact, (CFErrorRef *)&error)) {
LOGI(@"Can't remove entry: %@", [(__bridge NSError *)error localizedDescription]);
}
NSData *dataRef = UIImageJPEGRepresentation(image, 0.9f);
CFDataRef cfdata = CFDataCreate(NULL, [dataRef bytes], [dataRef length]);
[fab saveAddressBook];
if (!ABPersonSetImageData(_contact, cfdata, (CFErrorRef *)&error)) {
LOGI(@"Can't add entry: %@", [(__bridge NSError *)error localizedDescription]);
} else {
[fab saveAddressBook];
}
CFRelease(cfdata);
[FastAddressBook setAvatar:image forContact:_contact];
[_avatarImage setImage:[FastAddressBook imageForContact:_contact thumbnail:NO] bordered:NO withRoundedRadius:YES];
}
......
......@@ -27,9 +27,6 @@
@interface ContactsListTableView : UICheckBoxTableView {
@private
OrderedDictionary *addressBookMap;
NSMutableDictionary *avatarMap;
ABAddressBookRef addressBook;
}
- (void)loadData;
......
......@@ -25,17 +25,19 @@
@implementation ContactsListTableView
static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info, void *context);
#pragma mark - Lifecycle Functions
- (void)initContactsTableViewController {
addressBookMap = [[OrderedDictionary alloc] init];
avatarMap = [[NSMutableDictionary alloc] init];
addressBook = ABAddressBookCreateWithOptions(nil, nil);
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(onAddressBookUpdate:)
name:kLinphoneAddressBookUpdate
object:nil];
}
ABAddressBookRegisterExternalChangeCallback(addressBook, sync_address_book, (__bridge void *)(self));
- (void)onAddressBookUpdate:(NSNotification *)k {
[self loadData];
}
- (void)viewWillAppear:(BOOL)animated {
......@@ -65,9 +67,7 @@ static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info
}
- (void)dealloc {
if (addressBook) {
ABAddressBookUnregisterExternalChangeCallback(addressBook, sync_address_book, (__bridge void *)(self));
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark -
......@@ -92,7 +92,7 @@ static int ms_strcmpfuz(const char *fuzzy_word, const char *sentence) {
return (int)(within_sentence != NULL ? 0 : fuzzy_word + strlen(fuzzy_word) - c);
}
- (NSString *)displayNameForContact:(ABRecordRef)person {
- (NSString *)displayNameForContact:(Contact *)person {
NSString *name = [FastAddressBook displayNameForContact:person];
if (name != nil && [name length] > 0 && ![name isEqualToString:NSLocalizedString(@"Unknown", nil)]) {
// Add the contact only if it fuzzy match filter too (if any)
......@@ -116,27 +116,24 @@ static int ms_strcmpfuz(const char *fuzzy_word, const char *sentence) {
// Reset Address book
[addressBookMap removeAllObjects];
NSArray *lContacts = (NSArray *)CFBridgingRelease(ABAddressBookCopyArrayOfAllPeople(addressBook));
for (id lPerson in lContacts) {
for (NSString *addr in LinphoneManager.instance.fastAddressBook.addressBookMap) {
Contact *contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:addr];
BOOL add = true;
ABRecordRef person = (__bridge ABRecordRef)lPerson;
// Do not add the contact directly if we set some filter
if ([ContactSelection getSipFilter] || [ContactSelection emailFilterEnabled]) {
add = false;
}
if ([FastAddressBook contactHasValidSipDomain:person]) {
if ([FastAddressBook contactHasValidSipDomain:contact]) {
add = true;
}
if (!add && [ContactSelection emailFilterEnabled]) {
ABMultiValueRef personEmailAddresses = ABRecordCopyValue(person, kABPersonEmailProperty);
// Add this contact if it has an email
add = (ABMultiValueGetCount(personEmailAddresses) > 0);
CFRelease(personEmailAddresses);
add = (contact.emails.count > 0);
}
NSString *name = [self displayNameForContact:person];
NSString *name = [self displayNameForContact:contact];
if (add && name != nil) {
NSString *firstChar = [[name substringToIndex:1] uppercaseString];
......@@ -149,7 +146,7 @@ static int ms_strcmpfuz(const char *fuzzy_word, const char *sentence) {
subDic = [[OrderedDictionary alloc] init];
[addressBookMap insertObject:subDic forKey:firstChar selector:@selector(caseInsensitiveCompare:)];
}
[subDic insertObject:lPerson forKey:name selector:@selector(caseInsensitiveCompare:)];
[subDic insertObject:contact forKey:name selector:@selector(caseInsensitiveCompare:)];
}
}
[super loadData];
......@@ -166,13 +163,6 @@ static int ms_strcmpfuz(const char *fuzzy_word, const char *sentence) {
}
}
static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
ContactsListTableView *controller = (__bridge ContactsListTableView *)context;
ABAddressBookRevert(addressBook);
[controller->avatarMap removeAllObjects];
[controller loadData];
}
#pragma mark - UITableViewDataSource Functions
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
......@@ -200,14 +190,10 @@ static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info
if (cell == nil) {
cell = [[UIContactCell alloc] initWithIdentifier:kCellId];
}
ABRecordRef contact = [self contactForIndexPath:indexPath];
Contact *contact = [self contactForIndexPath:indexPath];
// Cached avatar
UIImage *image = [avatarMap objectForKey:[NSNumber numberWithInt:ABRecordGetRecordID(contact)]];
if (image == nil) {
image = [FastAddressBook imageForContact:contact thumbnail:true];
[avatarMap setObject:image forKey:[NSNumber numberWithInt:ABRecordGetRecordID(contact)]];
}
UIImage *image = [FastAddressBook imageForContact:contact thumbnail:true];
[cell.avatarImage setImage:image bordered:NO withRoundedRadius:YES];
[cell setContact:contact];
[super accessoryForCell:cell atPath:indexPath];
......@@ -236,15 +222,15 @@ static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
if (![self isEditing]) {
OrderedDictionary *subDic = [addressBookMap objectForKey:[addressBookMap keyAtIndex:[indexPath section]]];
ABRecordRef lPerson = (__bridge ABRecordRef)([subDic objectForKey:[subDic keyAtIndex:[indexPath row]]]);
Contact *contact = [subDic objectForKey:[subDic keyAtIndex:[indexPath row]]];
// Go to Contact details view
ContactDetailsView *view = VIEW(ContactDetailsView);
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
if (([ContactSelection getSelectionMode] != ContactSelectionModeEdit) || !([ContactSelection getAddAddress])) {
[view setContact:lPerson];
[view setContact:contact];
} else {
[view editContact:lPerson address:[ContactSelection getAddAddress]];
[view editContact:contact address:[ContactSelection getAddAddress]];
}
}
}
......@@ -253,39 +239,49 @@ static void sync_address_book(ABAddressBookRef addressBook, CFDictionaryRef info
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
ABAddressBookUnregisterExternalChangeCallback(addressBook, sync_address_book, (__bridge void *)(self));
[NSNotificationCenter.defaultCenter removeObserver:self];
[tableView beginUpdates];
NSString *firstChar = [addressBookMap keyAtIndex:[indexPath section]];
OrderedDictionary *subDic = [addressBookMap objectForKey:firstChar];
NSString *key = [[subDic allKeys] objectAtIndex:[indexPath row]];
ABRecordRef contact = (__bridge ABRecordRef)([subDic objectForKey:key]);
Contact *contact = [subDic objectForKey:key];
[[addressBookMap objectForKey:firstChar] removeObjectForKey:[self displayNameForContact:contact]];
if ([tableView numberOfRowsInSection:indexPath.section] == 1) {
[addressBookMap removeObjectForKey:firstChar];
[tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
withRowAnimation:UITableViewRowAnimationFade];
}
[[LinphoneManager.instance fastAddressBook] removeContact:contact];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
ABAddressBookRegisterExternalChangeCallback(addressBook, sync_address_book, (__bridge void *)(self));
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(onAddressBookUpdate:)
name:kLinphoneAddressBookUpdate
object:nil];
}
}
- (void)removeSelectionUsing:(void (^)(NSIndexPath *))remover {
[super removeSelectionUsing:^(NSIndexPath *indexPath) {
ABAddressBookUnregisterExternalChangeCallback(addressBook, sync_address_book, (__bridge void *)(self));
[NSNotificationCenter.defaultCenter removeObserver:self];
NSString *firstChar = [addressBookMap keyAtIndex:[indexPath section]];
OrderedDictionary *subDic = [addressBookMap objectForKey:firstChar];
NSString *key = [[subDic allKeys] objectAtIndex:[indexPath row]];
ABRecordRef contact = (__bridge ABRecordRef)([subDic objectForKey:key]);
Contact *contact = [subDic objectForKey:key];
[[addressBookMap objectForKey:firstChar] removeObjectForKey:[self displayNameForContact:contact]];
if ([self.tableView numberOfRowsInSection:indexPath.section] == 1) {
[addressBookMap removeObjectForKey:firstChar];
}
[[LinphoneManager.instance fastAddressBook] removeContact:contact];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(onAddressBookUpdate:)
name:kLinphoneAddressBookUpdate
object:nil];
}];
}
......
......@@ -140,7 +140,7 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onContactClick:(id)event {
LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog);
ABRecordRef contact = [FastAddressBook getContactWithAddress:addr];
Contact *contact = [FastAddressBook getContactWithAddress:addr];
if (contact) {
ContactDetailsView *view = VIEW(ContactDetailsView);
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
......
......@@ -26,7 +26,7 @@
@property(nonatomic, strong) IBOutlet UILabel *nameLabel;
@property(nonatomic, strong) IBOutlet UIRoundedImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UIImageView *linphoneImage;
@property (nonatomic, assign) ABRecordRef contact;
@property(nonatomic, assign) Contact *contact;
- (id)initWithIdentifier:(NSString*)identifier;
......
......@@ -49,7 +49,7 @@
#pragma mark - Property Functions
- (void)setContact:(ABRecordRef)acontact {
- (void)setContact:(Contact *)acontact {
_contact = acontact;
[ContactDisplay setDisplayNameLabel:_nameLabel forContact:_contact];
_linphoneImage.hidden = !([FastAddressBook contactHasValidSipDomain:_contact]);
......
......@@ -21,34 +21,35 @@
#import <AddressBook/AddressBook.h>
#include "linphone/linphonecore.h"
#include "Contact.h"
@interface FastAddressBook : NSObject {
ABAddressBookRef addressBook;
}
@interface FastAddressBook : NSObject
@property(readonly, nonatomic) NSMutableDictionary *addressBookMap;
- (void)reload;
- (void)saveAddressBook;
- (int)removeContact:(ABRecordRef)contact;
- (int)removeContact:(Contact *)contact;
- (BOOL)saveContact:(Contact *)contact;
+ (BOOL)isAuthorized;
// TOOLS
+ (ABRecordRef)getContactWithAddress:(const LinphoneAddress *)address;
+ (Contact *)getContactWithAddress:(const LinphoneAddress *)address;
+ (UIImage *)imageForContact:(ABRecordRef)contact thumbnail:(BOOL)thumbnail;
+ (UIImage *)imageForContact:(Contact *)contact thumbnail:(BOOL)thumbnail;
+ (UIImage *)imageForAddress:(const LinphoneAddress *)addr thumbnail:(BOOL)thumbnail;
+ (BOOL)contactHasValidSipDomain:(ABRecordRef)person;
+ (BOOL)contactHasValidSipDomain:(Contact *)person;
+ (NSString *)displayNameForContact:(ABRecordRef)person;
+ (NSString *)displayNameForContact:(Contact *)person;
+ (NSString *)displayNameForAddress:(const LinphoneAddress *)addr;
+ (BOOL)isSipURI:(NSString *)address; // should be removed
+ (NSString *)normalizeSipURI:(NSString *)address; // should be removed
+ (NSString *)localizedLabel:(NSString *)label;
+ (void)setAvatar:(UIImage *)avatar forContact:(Contact *)contact;
@end
This diff is collapsed.
......@@ -71,7 +71,7 @@ typedef enum {
@end
@interface ContactDisplay : NSObject
+ (void)setDisplayNameLabel:(UILabel *)label forContact:(ABRecordRef)contact;
+ (void)setDisplayNameLabel:(UILabel *)label forContact:(Contact *)contact;
+ (void)setDisplayNameLabel:(UILabel *)label forAddress:(const LinphoneAddress *)addr;
@end
......
......@@ -514,7 +514,7 @@
@implementation ContactDisplay
+ (void)setDisplayNameLabel:(UILabel *)label forContact:(ABRecordRef)contact {
+ (void)setDisplayNameLabel:(UILabel *)label forContact:(Contact *)contact {
label.text = [FastAddressBook displayNameForContact:contact];
#if 0
NSString *lLastName = CFBridgingRelease(ABRecordCopyValue(contact, kABPersonLastNameProperty));
......@@ -526,7 +526,7 @@
}
+ (void)setDisplayNameLabel:(UILabel *)label forAddress:(const LinphoneAddress *)addr {
const LinphoneFriend *contact = [FastAddressBook getContactWithAddress:addr];
Contact *contact = [FastAddressBook getContactWithAddress:addr];
if (contact) {
[ContactDisplay setDisplayNameLabel:label forContact:contact];
} else {
......
......@@ -27,3 +27,6 @@ ringer_dev_id=AQ: Audio Queue Device
[video]
display_filter_auto_rotate=0
[friend_0]
url="gautier.pelloux" <sip:gautier.pelloux@sip.linphone.org>
\ No newline at end of file
......@@ -9,6 +9,7 @@
#import "Log.h"
#import "Utils.h"
#import "Contact.h"
#import "UIToggleButton.h"
#import "UISpeakerButton.h"
......
bctoolbox @ e654c008
Subproject commit 0f29972001c023ead9041412c64d7f1ab46434f7
Subproject commit e654c008283c942345ac20540981ba854d9138ba
cmake-builder @ e9664d1b
Subproject commit d04ec58fa4c162634d32cce9ebcd3bdc3d07aa89
Subproject commit e9664d1bf5a2898635c3b29d0fc9a04ede221fc0
linphone @ 36fd57c4
Subproject commit 62703c016c4a75755ba074eda9e4b7a58d1241cd
Subproject commit 36fd57c45b9b6ebbafbe57e5be4c5e2120a281cf
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment