diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ca1ed085 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules +ios/RCTContacts.xcodeproj/xcuserdata +ios/RCTContacts.xcodeproj/project.xcworkspace + +# Android/IJ +.idea/workspace.xml +.idea/libraries +.gradle +local.properties +*.iml +build + +.npm-debug.log diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..d838da98 --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +examples/ diff --git a/README.md b/README.md new file mode 100644 index 00000000..e5e9e348 --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# React Native Contacts +Work in progress successor to react-native-addressbook + +## API +`getContacts` (callback) - returns *all* contacts as an array of objects +`addContact` (contact, callback) - adds a contact to the AddressBook. +`updateContact` (contact, callback) - where contact is an object with a valid recordID +`deleteContact` (contact, callback) - where contact is an object with a valid recordID + +####Permissions Methods (optional) +`checkPermission` (callback) - checks permission to use AddressBook. +`requestPermission` (callback) - request permission to use AddressBook. + +## Usage Example +```js +var AddressBook = require('react-native-addressbook') + +AddressBook.getContacts( (err, contacts) => { + if(err && err.type === 'permissionDenied'){ + // x.x + } + else{ + console.log(contacts) + } +}) +``` + +## Example Contact Record +```js +{ + recordID: 1, + lastName: "Jung", + firstName: "Carl", + middleName: "", + emailAddresses: [{ + label: "work", + email: "carl-jung@example.com", + }], + phoneNumbers: [{ + label: "mobile", + number: "(555) 555-5555", + }], + thumbnailPath: "", +} +``` + +## Adding Contacts +Currently all fields from the contact record except for thumbnailPath are supported for writing +```js +var newPerson = { + lastName: "Nietzsche", + firstName: "Friedrich", + emailAddresses: [{ + label: "work", + email: "mrniet@example.com", + }], +} + +AddressBook.addContact(newPerson, (err) => { /*...*/ }) +``` + +## Updating and Deleting Contacts +```js +//contrived example +AddressBook.getContacts( (err, contacts) => { + //update the first record + let someRecord = contacts[0] + someRecord.emailAddresses.push({ + label: "junk", + email: "mrniet+junkmail@test.com", + }) + AddressBook.updateContact(someRecord, (err) => { /*...*/ }) + + //delete the second record + AddressBook.deleteContact(contacts[1], (err) => { /*...*/ }) +}) +``` +Update and delete reference contacts by their recordID (as returned by the OS in getContacts). Apple does not guarantee the recordID will not change, e.g. it may be reassigned during a phone migration. Consequently you should always grab a fresh contact list with `getContacts` before performing update and delete operations. + +You can also delete a record using only it's recordID like follows: `AddressBook.deleteContact({recordID: 1}, (err) => {})}` + +##Permissions +Permissions will automatically be checked and if needed requested upon calling getContacts. If you need more granular control you can using the checkPermission and requestPermission methods as follows: +```js +AddressBook.checkPermission( (err, permission) => { + // AddressBook.PERMISSION_AUTHORIZED || AddressBook.PERMISSION_UNDEFINED || AddressBook.PERMISSION_DENIED + if(permission === 'undefined'){ + AddressBook.requestPermission( (err, permission) => { + // ... + }) + } + if(permission === 'authorized'){ + // yay! + } + if(permission === 'denied'){ + // x.x + } +}) +``` + +## Getting started +1. `npm install react-native-addressbook` +2. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]` +3. add `./node_modules/react-native-addressbook/RCTAddressBook.xcodeproj` +4. In the XCode project navigator, select your project, select the `Build Phases` tab and in the `Link Binary With Libraries` section add **libRCTAddressBook.a** + +## Todo +- [x] `checkPermission` & `requestPermission` +- [x] `getContacts` (get all contacts) +- [x] `addContact` +- [x] Update and Delete methods +- [ ] `getContacts` options (a la camera roll's getPhotos) +- [x] Automatic permission check & request in `getContacts` +- [ ] Contact Groups support + +## Credits +Thanks to @mattotodd whose [RCTAddressBook](https://github.com/mattotodd/react-native-addressbook-ios) this is largely derived from. diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 00000000..3724888f --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,34 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:1.1.3' + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.1" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + lintOptions { + abortOnError false + } +} + +repositories { + mavenCentral() +} + +dependencies { + compile 'com.facebook.react:react-native:0.12.+' +} diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a345fb8 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/src/main/java/com/rt2zz/reactnativecontacts/ContactsManager.java b/android/src/main/java/com/rt2zz/reactnativecontacts/ContactsManager.java new file mode 100644 index 00000000..77a98cee --- /dev/null +++ b/android/src/main/java/com/rt2zz/reactnativecontacts/ContactsManager.java @@ -0,0 +1,36 @@ +package com.rt2zz.reactnativecontacts; + +import android.provider.ContactsContract; +import android.content.ContentResolver; +import android.content.Context; + +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.widget.AdapterView; +import android.database.Cursor; + +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; + +import java.util.Map; + +public class ContactsManager extends ReactContextBaseJavaModule { + + public ContactsManager(ReactApplicationContext reactContext) { + super(reactContext); + } + + @ReactMethod + public void getContacts(Callback callback) { + callback.invoke("@TODO"); + } + + @Override + public String getName() { + return "ReactNativeContacts"; + } +} diff --git a/android/src/main/java/com/rt2zz/reactnativecontacts/ReactNativeContacts.java b/android/src/main/java/com/rt2zz/reactnativecontacts/ReactNativeContacts.java new file mode 100644 index 00000000..ca9f4fb8 --- /dev/null +++ b/android/src/main/java/com/rt2zz/reactnativecontacts/ReactNativeContacts.java @@ -0,0 +1,98 @@ +package com.rt2zz.reactnativecontacts; + +import java.util.*; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +public class ReactNativeContacts implements ReactPackage { + + @Override + public List createNativeModules( + ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new ContactsManager(reactContext)); + return modules; + } + + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Arrays.asList(); + } +} + + +// package com.rt2zz.reactnativecontacts; +// +// import com.facebook.react.bridge.NativeModule; +// import com.facebook.react.bridge.ReactApplicationContext; +// import com.facebook.react.bridge.Callback; +// import com.facebook.react.bridge.ReactContext; +// import com.facebook.react.bridge.ReactContextBaseJavaModule; +// import com.facebook.react.bridge.ReactMethod; +// +// import java.util.Map; +// +// public class ReactNativeContacts extends ReactContextBaseJavaModule { +// +// public ReactNativeContacts(ReactApplicationContext reactContext) { +// super(reactContext); +// } +// +// @ReactMethod +// public void getContacts(Callback callback, int duration) { +// callback.invoke("hi"); +// } +// +// @Override +// public String getName() { +// return "ReactNativeContacts"; +// } +// } + +// package com.rt2zz.reactnativecontacts; +// +// import com.facebook.react.ReactPackage; +// import com.facebook.react.bridge.JavaScriptModule; +// import com.facebook.react.bridge.NativeModule; +// import com.facebook.react.bridge.Callback; +// import com.facebook.react.bridge.ReactApplicationContext; +// import com.facebook.react.uimanager.ViewManager; +// +// import java.util.ArrayList; +// import java.util.Arrays; +// import java.util.Collections; +// import java.util.List; +// +// public class ReactNativeContacts implements ReactPackage { +// +// public ReactNativeContacts() { +// } +// +// @Override +// public List createNativeModules( +// ReactApplicationContext reactContext) { +// return new ArrayList<>(); +// } +// +// @Override +// public List> createJSModules() { +// return Collections.emptyList(); +// } +// +// @Override +// public List createViewManagers(ReactApplicationContext reactContext) { +// return Arrays.asList( +// new IconManager(mAllIconFonts) +// ); +// } +// } diff --git a/index.js b/index.js new file mode 100644 index 00000000..7a88cb26 --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +var ReactNative = require('react-native') +console.log('rnn', ReactNative.NativeModules) +module.exports = ReactNative.NativeModules.ReactNativeContacts diff --git a/ios/RCTContacts.xcodeproj/project.pbxproj b/ios/RCTContacts.xcodeproj/project.pbxproj new file mode 100644 index 00000000..13a4c6fa --- /dev/null +++ b/ios/RCTContacts.xcodeproj/project.pbxproj @@ -0,0 +1,274 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 144161911BD0A79300FA4F59 /* RCTContacts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 144161901BD0A79300FA4F59 /* RCTContacts.h */; }; + 144161931BD0A79300FA4F59 /* RCTContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 144161921BD0A79300FA4F59 /* RCTContacts.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 1441618C1BD0A79300FA4F59 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + 144161911BD0A79300FA4F59 /* RCTContacts.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1441618E1BD0A79300FA4F59 /* libRCTContacts.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTContacts.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 144161901BD0A79300FA4F59 /* RCTContacts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTContacts.h; sourceTree = ""; }; + 144161921BD0A79300FA4F59 /* RCTContacts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTContacts.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1441618B1BD0A79300FA4F59 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1441618F1BD0A79300FA4F59 /* RCTContacts */ = { + isa = PBXGroup; + children = ( + 144161901BD0A79300FA4F59 /* RCTContacts.h */, + 144161921BD0A79300FA4F59 /* RCTContacts.m */, + ); + path = RCTContacts; + sourceTree = ""; + }; + 145CC5671AED80100006342E = { + isa = PBXGroup; + children = ( + 1441618F1BD0A79300FA4F59 /* RCTContacts */, + 145CC5711AED80100006342E /* Products */, + ); + sourceTree = ""; + }; + 145CC5711AED80100006342E /* Products */ = { + isa = PBXGroup; + children = ( + 1441618E1BD0A79300FA4F59 /* libRCTContacts.a */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1441618D1BD0A79300FA4F59 /* RCTContacts */ = { + isa = PBXNativeTarget; + buildConfigurationList = 144161941BD0A79300FA4F59 /* Build configuration list for PBXNativeTarget "RCTContacts" */; + buildPhases = ( + 1441618A1BD0A79300FA4F59 /* Sources */, + 1441618B1BD0A79300FA4F59 /* Frameworks */, + 1441618C1BD0A79300FA4F59 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RCTContacts; + productName = RCTContacts; + productReference = 1441618E1BD0A79300FA4F59 /* libRCTContacts.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 145CC5681AED80100006342E /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0630; + ORGANIZATIONNAME = rt2zz; + TargetAttributes = { + 1441618D1BD0A79300FA4F59 = { + CreatedOnToolsVersion = 7.0.1; + }; + }; + }; + buildConfigurationList = 145CC56B1AED80100006342E /* Build configuration list for PBXProject "RCTContacts" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 145CC5671AED80100006342E; + productRefGroup = 145CC5711AED80100006342E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1441618D1BD0A79300FA4F59 /* RCTContacts */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 1441618A1BD0A79300FA4F59 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 144161931BD0A79300FA4F59 /* RCTContacts.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 144161951BD0A79300FA4F59 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_TESTABILITY = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../../react-native/React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 144161961BD0A79300FA4F59 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../../react-native/React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 145CC5821AED80100006342E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/node_modules/react-native/Libraries/**", + "$(SRCROOT)/node_modules/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 145CC5831AED80100006342E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/node_modules/react-native/Libraries/**", + "$(SRCROOT)/node_modules/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 144161941BD0A79300FA4F59 /* Build configuration list for PBXNativeTarget "RCTContacts" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 144161951BD0A79300FA4F59 /* Debug */, + 144161961BD0A79300FA4F59 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 145CC56B1AED80100006342E /* Build configuration list for PBXProject "RCTContacts" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 145CC5821AED80100006342E /* Debug */, + 145CC5831AED80100006342E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 145CC5681AED80100006342E /* Project object */; +} diff --git a/ios/RCTContacts/RCTContacts.h b/ios/RCTContacts/RCTContacts.h new file mode 100644 index 00000000..d1a89599 --- /dev/null +++ b/ios/RCTContacts/RCTContacts.h @@ -0,0 +1,5 @@ +#import "RCTBridgeModule.h" + +@interface RCTContacts : NSObject + +@end diff --git a/ios/RCTContacts/RCTContacts.m b/ios/RCTContacts/RCTContacts.m new file mode 100644 index 00000000..2b4f11b1 --- /dev/null +++ b/ios/RCTContacts/RCTContacts.m @@ -0,0 +1,284 @@ +#import +#import +#import "RCTContacts.h" + +@implementation RCTContacts + +RCT_EXPORT_MODULE(); + +- (NSDictionary *)constantsToExport +{ + return @{ + @"PERMISSION_DENIED": @"denied", + @"PERMISSION_AUTHORIZED": @"authorized", + @"PERMISSION_UNDEFINED": @"undefined" + }; +} + +RCT_EXPORT_METHOD(checkPermission:(RCTResponseSenderBlock) callback) +{ + int authStatus = ABAddressBookGetAuthorizationStatus(); + if ( authStatus == kABAuthorizationStatusDenied || authStatus == kABAuthorizationStatusRestricted){ + callback(@[[NSNull null], @"denied"]); + } else if (authStatus == kABAuthorizationStatusAuthorized){ + callback(@[[NSNull null], @"authorized"]); + } else { //ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined + callback(@[[NSNull null], @"undefined"]); + } +} + +RCT_EXPORT_METHOD(requestPermission:(RCTResponseSenderBlock) callback) +{ + ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error) { + if (!granted){ + [self checkPermission:callback]; + return; + } + [self checkPermission:callback]; + }); +} + +RCT_EXPORT_METHOD(getContacts:(RCTResponseSenderBlock) callback) +{ + ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, nil); + int authStatus = ABAddressBookGetAuthorizationStatus(); + if(authStatus != kABAuthorizationStatusAuthorized){ + ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) { + if(granted){ + [self retrieveContactsFromAddressBook:addressBookRef withCallback:callback]; + }else{ + NSDictionary *error = @{ + @"type": @"permissionDenied" + }; + callback(@[error, [NSNull null]]); + } + }); + } + else{ + [self retrieveContactsFromAddressBook:addressBookRef withCallback:callback]; + } +} + +-(void) retrieveContactsFromAddressBook:(ABAddressBookRef)addressBookRef +withCallback:(RCTResponseSenderBlock) callback +{ + NSArray *allContacts = (__bridge_transfer NSArray *)ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering(addressBookRef, NULL, kABPersonSortByLastName); + int totalContacts = (int)[allContacts count]; + int currentIndex = 0; + int maxIndex = --totalContacts; + + NSMutableArray *contacts = [[NSMutableArray alloc] init]; + + while (currentIndex <= maxIndex){ + NSDictionary *contact = [self dictionaryRepresentationForABPerson: (ABRecordRef)[allContacts objectAtIndex:(long)currentIndex]]; + + if(contact){ + [contacts addObject:contact]; + } + currentIndex++; + } + callback(@[[NSNull null], contacts]); +} + +-(NSDictionary*) dictionaryRepresentationForABPerson:(ABRecordRef) person +{ + NSMutableDictionary* contact = [NSMutableDictionary dictionary]; + + NSNumber *recordID = [NSNumber numberWithInteger:(ABRecordGetRecordID(person))]; + NSString *firstName = (__bridge_transfer NSString *)(ABRecordCopyValue(person, kABPersonFirstNameProperty)); + NSString *lastName = (__bridge_transfer NSString *)(ABRecordCopyValue(person, kABPersonLastNameProperty)); + NSString *middleName = (__bridge_transfer NSString *)(ABRecordCopyValue(person, kABPersonMiddleNameProperty)); + + [contact setObject: recordID forKey: @"recordID"]; + + BOOL hasName = false; + if (firstName) { + [contact setObject: firstName forKey:@"firstName"]; + hasName = true; + } + + if (lastName) { + [contact setObject: lastName forKey:@"lastName"]; + hasName = true; + } + + if(middleName){ + [contact setObject: (middleName) ? middleName : @"" forKey:@"middleName"]; + } + + if(!hasName){ + //nameless contact, do not include in results + return nil; + } + + //handle phone numbers + NSMutableArray *phoneNumbers = [[NSMutableArray alloc] init]; + + ABMultiValueRef multiPhones = ABRecordCopyValue(person, kABPersonPhoneProperty); + for(CFIndex i=0;i + +@interface libRCTContacts : NSObject + +@end diff --git a/ios/libRCTContacts/libRCTContacts.m b/ios/libRCTContacts/libRCTContacts.m new file mode 100644 index 00000000..7d3a86c8 --- /dev/null +++ b/ios/libRCTContacts/libRCTContacts.m @@ -0,0 +1,13 @@ +// +// libRCTContacts.m +// libRCTContacts +// +// Created by Zack on 10/15/15. +// Copyright © 2015 rt2zz. All rights reserved. +// + +#import "libRCTContacts.h" + +@implementation libRCTContacts + +@end diff --git a/package.json b/package.json new file mode 100644 index 00000000..4dac8174 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "react-native-contacts", + "repository": { + "type": "git", + "url": "https://github.com/rt2zz/react-native-contacts.git" + }, + "version": "0.0.1", + "description": "React Native Contacts (android & ios)", + "nativePackage": true, + "keywords": [ + "react-native", + "react", + "react-component", + "addressbook", + "contacts" + ], + "bugs": { + "url": "https://github.com/rt2zz/react-native-contacts/issues" + }, + "homepage": "https://github.com/rt2zz/react-native-contacts", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "rt2zz ", + "license": "MIT" +}