diff --git a/README.md b/README.md index 641bd0c..d5dd979 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # react-native-contacts-wrapper +> This project is just a fork of https://github.com/LynxITDigital/react-native-contacts-wrapper with https://github.com/LynxITDigital/react-native-contacts-wrapper/pull/27 +> Please refer to original project for issues/etc. + ![alt tag](https://github.com/LynxITDigital/Screenshots/blob/master/RN%20Contacts%20Wrapper%20example.gif) -This is a simple wrapper for the native iOS and Android Contact Picker UIs. When calling the API functions, the appropriate picker is launched. If a contact is picked, the promise is resolved with the requested data about the picked contact. +This is a simple wrapper for the native iOS and Android Contact Picker UIs. When calling the API functions, the appropriate picker is launched. If a contact is picked, the promise is resolved with the requested data about the picked contact. This uses the ContactsContract API for Android, AddressBook library for iOS8 and below and the new Contacts library for ios9+. -The API is currently very basic. This was started just as a way of selecting a contact's email address. The getContact function was added as a more generic way of returning contact data. Currently this returns Name, Phone and Email for picked contact. In future more fields will be added to this, and possibly more specific methods similar to getEmail. +The API is currently very basic. This was started just as a way of selecting a contact's email address. The getContact function was added as a more generic way of returning contact data. Currently this returns Name, Phone and Email for picked contact. In future more fields will be added to this, and possibly more specific methods similar to getEmail. Feel free to extend the functionality so it's more useful for everyone - all PRs welcome! @@ -18,12 +21,13 @@ If you have rnpm installed, all you need to do is ``` npm install react-native-contacts-wrapper --save -rnpm link react-native-contacts-wrapper +react-native link react-native-contacts-wrapper ``` ### Manual #### Android (with RN 0.29 and above) + in `settings.gradle` ``` @@ -57,19 +61,18 @@ protected List getPackages() { in `AndroidManifest.xml` make sure you have the following setting even if you have done `react-native upgrade` + ``` ``` - #### iOS 1. Open your xCode project @@ -83,13 +86,11 @@ Also add 9. In same screen, click + again, you should now see the .a file for you project, Add this 10. Clean and Rebuild your Xcode project - ##API -`getContact` (Promise) - returns basic contact data as a JS object. Currently returns name, first phone number and first email for contact. +`getContact` (Promise) - returns basic contact data as a JS object. Currently returns name, first phone number and first email for contact. `getEmail` (Promise) - returns first email address (if found) for contact as string. - ##Usage Methods should be called from React Native as any other promise. diff --git a/android/app/src/main/java/com/lynxit/contactswrapper/ContactsWrapper.java b/android/app/src/main/java/com/lynxit/contactswrapper/ContactsWrapper.java index 487ab81..fc7e1d5 100644 --- a/android/app/src/main/java/com/lynxit/contactswrapper/ContactsWrapper.java +++ b/android/app/src/main/java/com/lynxit/contactswrapper/ContactsWrapper.java @@ -28,6 +28,7 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableArray; import com.facebook.react.uimanager.ViewManager; public class ContactsWrapper extends ReactContextBaseJavaModule implements ActivityEventListener { @@ -58,12 +59,15 @@ public class ContactsWrapper extends ReactContextBaseJavaModule implements Activ add(ContactsContract.CommonDataKinds.Email.TYPE); add(ContactsContract.CommonDataKinds.Email.LABEL); }}; + private Context context; public ContactsWrapper(ReactApplicationContext reactContext) { super(reactContext); this.contentResolver = getReactApplicationContext().getContentResolver(); reactContext.addActivityEventListener(this); + this.context = reactContext; + } @Override @@ -109,8 +113,8 @@ private void launchPicker(Promise contactsPromise, int requestCode) { public void onActivityResult(Activity ContactsWrapper, final int requestCode, final int resultCode, final Intent intent) { if(mContactsPromise == null || mCtx == null - || (requestCode != CONTACT_REQUEST && requestCode != EMAIL_REQUEST)){ - return; + || (requestCode != CONTACT_REQUEST && requestCode != EMAIL_REQUEST)){ + return; } String email = null; @@ -125,7 +129,12 @@ public void onActivityResult(Activity ContactsWrapper, final int requestCode, fi //First get ID String id = null; int idx; + WritableMap contactData = Arguments.createMap(); + WritableArray phonesArray = Arguments.createArray(); + WritableArray emailsArray = Arguments.createArray(); + + Cursor cursor = this.contentResolver.query(contactUri, null, null, null, null); if (cursor != null && cursor.moveToFirst()) { idx = cursor.getColumnIndex(ContactsContract.Contacts._ID); @@ -143,8 +152,8 @@ public void onActivityResult(Activity ContactsWrapper, final int requestCode, fi // Create the projection (SQL fields) and sort order. String[] projection = { - ContactsContract.Contacts.Entity.MIMETYPE, - ContactsContract.Contacts.Entity.DATA1 + ContactsContract.Contacts.Entity.MIMETYPE, + ContactsContract.Contacts.Entity.DATA1 }; String sortOrder = ContactsContract.Contacts.Entity.RAW_CONTACT_ID + " ASC"; cursor = this.contentResolver.query(contactUri, projection, null, null, sortOrder); @@ -155,9 +164,71 @@ public void onActivityResult(Activity ContactsWrapper, final int requestCode, fi /* Map Any contact data we want returned to the JS object key for React Native */ HashMap returnKeys = new HashMap(); returnKeys.put(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, "name"); - returnKeys.put(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, "phone"); - returnKeys.put(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, "email"); + // loops through all phones + + Cursor phones = this.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, + ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + id, null, null); + while (phones.moveToNext()) + { + WritableMap phoneObj = Arguments.createMap(); + String number = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); + int type = phones.getInt(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE)); + String phoneType = ""; + switch (type) { + case ContactsContract.CommonDataKinds.Phone.TYPE_HOME: + // do something with the Home number here.. + phoneType = " home"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE: + // do something with the Mobile number here... + phoneType = "mobile"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_WORK: + phoneType = "work"; + // do something with the Work number here... + break; + } + phoneObj.putString("number", number); + phoneObj.putString("number_type", phoneType); + phonesArray.pushMap(phoneObj); + } + phones.close(); + + // loops through all emails + Cursor emails = this.contentResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, + ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + id, null, null); + while (emails.moveToNext()) + { + WritableMap emailObj = Arguments.createMap(); + String emailAddress = emails.getString(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS)); + int type = emails.getInt(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.TYPE)); + String emailType = ""; + switch (type) { + case ContactsContract.CommonDataKinds.Email.TYPE_HOME: + // do something with the Home number here.. + emailType = "home"; + break; + case ContactsContract.CommonDataKinds.Email.TYPE_MOBILE: + // do something with the Mobile number here... + emailType = "mobile"; + break; + case ContactsContract.CommonDataKinds.Email.TYPE_WORK: + emailType = "work"; + // do something with the Work number here... + break; + } + Log.w(">>>>>>>>>>>>>>>>>>>>>>>>>>>>", emailType); + emailObj.putString("address", emailAddress); + emailObj.putString("address_type", emailType); + emailsArray.pushMap(emailObj); + } + emails.close(); + + contactData.putArray("emails", emailsArray); + contactData.putArray("phones", phonesArray); + + // this now only grabs the name of the contact int dataIdx = cursor.getColumnIndex(ContactsContract.Contacts.Entity.DATA1); int mimeIdx = cursor.getColumnIndex(ContactsContract.Contacts.Entity.MIMETYPE); if (cursor.moveToFirst()) { @@ -172,6 +243,7 @@ public void onActivityResult(Activity ContactsWrapper, final int requestCode, fi cursor.close(); if(foundData) { + // send contact back mContactsPromise.resolve(contactData); return; } else { @@ -193,8 +265,8 @@ public void onActivityResult(Activity ContactsWrapper, final int requestCode, fi // query for everything email Cursor cursor = mCtx.getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, - null, ContactsContract.CommonDataKinds.Email.CONTACT_ID + "=?", new String[]{id}, - null); + null, ContactsContract.CommonDataKinds.Email.CONTACT_ID + "=?", new String[]{id}, + null); int emailIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA); diff --git a/ios/RCTContactsWrapper/RCTContactsWrapper.m b/ios/RCTContactsWrapper/RCTContactsWrapper.m index 72243ca..d1b73cc 100644 --- a/ios/RCTContactsWrapper/RCTContactsWrapper.m +++ b/ios/RCTContactsWrapper/RCTContactsWrapper.m @@ -31,10 +31,10 @@ @implementation RCTContactsWrapper self._resolve = resolve; self._reject = reject; _requestCode = REQUEST_CONTACT; - + [self launchContacts]; - - + + } /* Get ontact email as string */ @@ -43,10 +43,10 @@ @implementation RCTContactsWrapper self._resolve = resolve; self._reject = reject; _requestCode = REQUEST_EMAIL; - + [self launchContacts]; - - + + } @@ -54,9 +54,10 @@ @implementation RCTContactsWrapper Launch the contacts UI */ -(void) launchContacts { - + UIViewController *picker; if([CNContactPickerViewController class]) { + //if(NSClassFromString(@"CNContactPickerViewController")){ //iOS 9+ picker = [[CNContactPickerViewController alloc] init]; ((CNContactPickerViewController *)picker).delegate = self; @@ -65,16 +66,14 @@ -(void) launchContacts { picker = [[ABPeoplePickerNavigationController alloc] init]; [((ABPeoplePickerNavigationController *)picker) setPeoplePickerDelegate:self]; } - //Launch Contact Picker or Address Book View Controller - UIViewController *root = [[[UIApplication sharedApplication] delegate] window].rootViewController; - BOOL modalPresent = (BOOL) (root.presentedViewController); - if (modalPresent) { - UIViewController *parent = root.presentedViewController; - [parent presentViewController:picker animated:YES completion:nil]; - } else { - [root presentViewController:picker animated:YES completion:nil]; - } + // Dispatch the UI on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + //Launch Contact Picker or Address Book View Controller + UIViewController *root = [[[UIApplication sharedApplication] delegate] window].rootViewController; + [root presentViewController:picker animated:YES completion:nil]; + }); + } @@ -107,7 +106,11 @@ -(void)contactPicked:(NSDictionary *)contactData { - (NSMutableDictionary *) emptyContactDict { - return [[NSMutableDictionary alloc] initWithObjects:@[@"", @"", @""] forKeys:@[@"name", @"phone", @"email"]]; + return [ + [NSMutableDictionary alloc] + initWithObjects:@[@"", @"", @"", @"", @"", @""] + forKeys:@[@"givenName", @"middleName", @"familyName", @"fullName", @"phoneNumbers", @"email"] + ]; } /** @@ -115,52 +118,65 @@ - (NSMutableDictionary *) emptyContactDict { */ -(NSString *) getFullNameForFirst:(NSString *)fName middle:(NSString *)mName last:(NSString *)lName { //Check whether to include middle name or not - NSArray *names = (mName.length > 0) ? [NSArray arrayWithObjects:fName, mName, lName, nil] : [NSArray arrayWithObjects:fName, lName, nil];; + NSArray *names = (mName.length > 0) + ? [NSArray arrayWithObjects:fName, mName, lName, nil] + : [NSArray arrayWithObjects:fName, lName, nil]; return [names componentsJoinedByString:@" "]; } #pragma mark - Event handlers - iOS 9+ + - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact { switch(_requestCode){ case REQUEST_CONTACT: { /* Return NSDictionary ans JS Object to RN, containing basic contact data This is a starting point, in future more fields should be added, as required. - This could also be extended to return arrays of phone numbers, email addresses etc. instead of jsut first found */ NSMutableDictionary *contactData = [self emptyContactDict]; - + NSString *fullName = [self getFullNameForFirst:contact.givenName middle:contact.middleName last:contact.familyName ]; - NSArray *phoneNos = contact.phoneNumbers; - NSArray *emailAddresses = contact.emailAddresses; - + //Return full name - [contactData setValue:fullName forKey:@"name"]; - - //Return first phone number - if([phoneNos count] > 0) { - CNPhoneNumber *phone = ((CNLabeledValue *)phoneNos[0]).value; - [contactData setValue:phone.stringValue forKey:@"phone"]; + [contactData setValue:fullName forKey:@"fullName"]; + [contactData setValue:contact.givenName forKey:@"givenName"]; + [contactData setValue:contact.middleName forKey:@"middleName"]; + [contactData setValue:contact.familyName forKey:@"familyName"]; + + NSMutableArray *phoneNumbers = [[NSMutableArray alloc] init]; + for(CFIndex i=0;i<[contact.phoneNumbers count];i++) { + NSMutableDictionary* phone = [NSMutableDictionary dictionary]; + CNLabeledValue *lv = ((CNLabeledValue *)contact.phoneNumbers[i]); + [phone setValue: ((CNPhoneNumber *)lv.value).stringValue forKey:@"number"]; + [phone setValue: [CNLabeledValue localizedStringForLabel:lv.label] forKey:@"label"]; + [phoneNumbers addObject:phone]; } - - //Return first email address - if([emailAddresses count] > 0) { - [contactData setValue:((CNLabeledValue *)emailAddresses[0]).value forKey:@"email"]; + [contactData setObject:phoneNumbers forKey:@"phoneNumbers"]; + + + NSMutableArray *emailAddresses = [[NSMutableArray alloc] init]; + for(CFIndex i=0;i<[contact.emailAddresses count];i++) { + NSMutableDictionary* email = [NSMutableDictionary dictionary]; + CNLabeledValue *lv = ((CNLabeledValue *)contact.emailAddresses[i]); + [email setValue: lv.value forKey:@"email"]; + [email setValue: [CNLabeledValue localizedStringForLabel:lv.label] forKey:@"label"]; + [emailAddresses addObject:email]; } - + [contactData setObject:emailAddresses forKey:@"emailAddresses"]; + [self contactPicked:contactData]; } break; case REQUEST_EMAIL : { - /* Return Only email address as string */ + // Return Only email address as string if([contact.emailAddresses count] < 1) { [self pickerNoEmail]; return; } - + CNLabeledValue *email = contact.emailAddresses[0].value; [self emailPicked:email]; } @@ -170,8 +186,8 @@ - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:( [self pickerError]; break; } - - + + } @@ -188,7 +204,7 @@ - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)p switch(_requestCode) { case(REQUEST_CONTACT): { - + /* Return NSDictionary ans JS Object to RN, containing basic contact data This is a starting point, in future more fields should be added, as required. This could also be extended to return arrays of phone numbers, email addresses etc. instead of jsut first found @@ -198,26 +214,63 @@ - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)p fNameObject = (__bridge NSString *) ABRecordCopyValue(person, kABPersonFirstNameProperty); mNameObject = (__bridge NSString *) ABRecordCopyValue(person, kABPersonMiddleNameProperty); lNameObject = (__bridge NSString *) ABRecordCopyValue(person, kABPersonLastNameProperty); - + NSString *fullName = [self getFullNameForFirst:fNameObject middle:mNameObject last:lNameObject]; - + //Return full name - [contactData setValue:fullName forKey:@"name"]; - - //Return first phone number - ABMultiValueRef phoneMultiValue = ABRecordCopyValue(person, kABPersonPhoneProperty); - NSArray *phoneNos = (__bridge NSArray *)ABMultiValueCopyArrayOfAllValues(phoneMultiValue); - if([phoneNos count] > 0) { - [contactData setValue:phoneNos[0] forKey:@"phone"]; + [contactData setValue:fullName forKey:@"fullName"]; + [contactData setValue:fNameObject forKey:@"givenName"]; + [contactData setValue:mNameObject forKey:@"middleName"]; + [contactData setValue:lNameObject forKey:@"familyName"]; + + //Return first phone numbers + NSMutableArray *phoneNumberList = [[NSMutableArray alloc] init]; + + ABMultiValueRef phoneNumbers = ABRecordCopyValue(person, kABPersonPhoneProperty); + if (phoneNumbers) { + CFIndex numberOfPhoneNumbers = ABMultiValueGetCount(phoneNumbers); + for (CFIndex i = 0; i < numberOfPhoneNumbers; i++) { + NSMutableDictionary* phone = [NSMutableDictionary dictionary]; + CFStringRef label = ABMultiValueCopyLabelAtIndex(phoneNumbers, i); + CFStringRef number = ABMultiValueCopyValueAtIndex(phoneNumbers, i); + if (number) { + if (label){ + NSString *l=(__bridge NSString *)ABAddressBookCopyLocalizedLabel(label); + [phone setValue: l forKey:@"label"]; + } + else [phone setValue: @"Other" forKey:@"label"]; + [phone setValue: (__bridge NSString *)number forKey:@"number"]; + [phoneNumberList addObject:phone]; + } + } + CFRelease(phoneNumbers); } - - //Return first email - ABMultiValueRef emailMultiValue = ABRecordCopyValue(person, kABPersonEmailProperty); - NSArray *emailAddresses = (__bridge NSArray *)ABMultiValueCopyArrayOfAllValues(emailMultiValue); - if([emailAddresses count] > 0) { - [contactData setValue:emailAddresses[0] forKey:@"email"]; + + [contactData setObject:phoneNumberList forKey:@"phoneNumbers"]; + + //Return email list + NSMutableArray *emailList = [[NSMutableArray alloc] init]; + + ABMultiValueRef emails = ABRecordCopyValue(person, kABPersonEmailProperty); + if (emails) { + CFIndex numberOfEmails = ABMultiValueGetCount(emails); + for (CFIndex i = 0; i < numberOfEmails; i++) { + NSMutableDictionary* email = [NSMutableDictionary dictionary]; + CFStringRef label = ABMultiValueCopyLabelAtIndex(emails, i); + CFStringRef value = ABMultiValueCopyValueAtIndex(emails, i); + if (value) { + if (label){ + NSString *l=(__bridge NSString *)ABAddressBookCopyLocalizedLabel(label); + [email setValue: l forKey:@"label"]; + } + else [email setValue: @"Other" forKey:@"label"]; + [email setValue: (__bridge NSString *) value forKey:@"email"]; + [emailList addObject:email]; + } + } + CFRelease(emails); } - + [contactData setObject:emailList forKey:@"emailAddresses"]; [self contactPicked:contactData]; } @@ -231,7 +284,7 @@ - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)p [self pickerNoEmail]; return; } - + [self emailPicked:emailAddresses[0]]; } break; @@ -241,7 +294,7 @@ - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)p [self pickerError]; return; } - + } - (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker { diff --git a/package.json b/package.json index 6cf8e79..4e2cc88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-native-contacts-wrapper", - "version": "0.2.4", + "name": "react-native-contacts-wrapper-with-labels", + "version": "0.4.0", "nativePackage": true, "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start"