Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages

name: Node.js Package

on:
release:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test

publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
118 changes: 65 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,28 @@ This is a simple wrapper for the native iOS and Android Contact Picker UIs, with
### Installation

```
yarn add react-native-select-contact
yarn add react-native-select-contact@https://github.com/chen834921478/react-native-select-contact.git


```

or with NPM

```
npm install react-native-select-contact
npm install react-native-select-contact@https://github.com/chen834921478/react-native-select-contact.git

```

For React Native => 0.59 only:

```
react-native link react-native-select-contact
```

#### Android

For Android support, make sure your manifest file includes permission to read contacts along with a query intent for Android 11+:

```xml
<manifest>
<!-- Add this for overall Android support -->
Expand All @@ -38,10 +46,13 @@ For Android support, make sure your manifest file includes permission to read co
</queries>
</manifest>
```

Also, in addition to declaring `READ_CONTACTS` permission in `AndroidManifest.xml`, you also need to explicitly request for the permission at runtime. So make sure `READ_CONTACT` permission is granted at runtime before calling API method(s).

#### iOS

For iOS support, make sure to include usage description for contacts in your `Info.plist` file

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
Expand All @@ -67,12 +78,12 @@ selectContactEmail(): Promise<ContactEmailSelection | null>;
selectContactPostalAddress(): Promise<ContactPostalAddressSelection | null>;
```

These methods all open up a separate ViewController (on IOS) or Activity (on Android) to select a contact. See Types below.
These methods all open up a separate ViewController (on IOS) or Activity (on Android) to select a contact. See Types below.

For `selectContactPhone`, `selectContactEmail`, or `selectContactPostalAddress`, if there are more than one phone or email, an `ActionSheetIOS` is
shown for IOS, and the first entry is returned for Android.

A return value `null` may be because the user cancelled the contact selection. You shouldn't need to worry about doing
A return value `null` may be because the user cancelled the contact selection. You shouldn't need to worry about doing
anything if the promise resolves to `null`.

#### Optional Android ActionSheet
Expand All @@ -84,6 +95,7 @@ yarn add react-native-action-sheet
```

For React Native => 0.59 only:

```
react-native link react-native-action-sheet
```
Expand All @@ -95,80 +107,80 @@ when there are more than one phone number or email on a selected contact.

```typescript
interface PhoneEntry {
number: string,
type: string
number: string;
type: string;
}

interface EmailEntry {
address: string,
type: string
address: string;
type: string;
}

interface AddressEntry {
formattedAddress: string, // android only
type: string, // android only
street: string,
city: string,
state: string,
postalCode: string,
isoCountryCode: string
formattedAddress: string; // android only
type: string; // android only
street: string;
city: string;
state: string;
postalCode: string;
isoCountryCode: string;
}

interface Contact {
name: string,
phones: PhoneEntry[],
emails: EmailEntry[],
postalAddresses: AddressEntry[]
name: string;
phones: PhoneEntry[];
emails: EmailEntry[];
postalAddresses: AddressEntry[];
}

interface ContactPhoneSelection {
contact: Contact,
selectedPhone: PhoneEntry
contact: Contact;
selectedPhone: PhoneEntry;
}

interface ContactEmailSelection {
contact: Contact,
selectedEmail: EmailEntry
contact: Contact;
selectedEmail: EmailEntry;
}

interface ContactPostalAddressSelection {
contact: Contact,
selectedAddress: AddressEntry
contact: Contact;
selectedAddress: AddressEntry;
}
```

### Example

```javascript

import { selectContactPhone } from 'react-native-select-contact';
import { PermissionsAndroid, Platform } from 'react-native';
import { selectContactPhone } from "react-native-select-contact";
import { PermissionsAndroid, Platform } from "react-native";

async function getPhoneNumber() {
// on android we need to explicitly request for contacts permission and make sure it's granted
// before calling API methods
if (Platform.OS === 'android') {
const request = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
);

// denied permission
if (request === PermissionsAndroid.RESULTS.DENIED) throw Error("Permission Denied");

// user chose 'deny, don't ask again'
else if (request === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) throw Error("Permission Denied");
}

// Here we are sure permission is granted for android or that platform is not android
const selection = await selectContactPhone();
if (!selection) {
return null;
}

let { contact, selectedPhone } = selection;
console.log(`Selected ${selectedPhone.type} phone number ${selectedPhone.number} from ${contact.name}`);
return selectedPhone.number;
// on android we need to explicitly request for contacts permission and make sure it's granted
// before calling API methods
if (Platform.OS === "android") {
const request = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
);

// denied permission
if (request === PermissionsAndroid.RESULTS.DENIED)
throw Error("Permission Denied");
// user chose 'deny, don't ask again'
else if (request === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN)
throw Error("Permission Denied");
}

// Here we are sure permission is granted for android or that platform is not android
const selection = await selectContactPhone();
if (!selection) {
return null;
}

let { contact, selectedPhone } = selection;
console.log(
`Selected ${selectedPhone.type} phone number ${selectedPhone.number} from ${contact.name}`,
);
return selectedPhone.number;
}


```
2 changes: 0 additions & 2 deletions android/.gitignore

This file was deleted.

4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ buildscript {
if (project == rootProject) {
repositories {
google()
mavencentral()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
Expand Down Expand Up @@ -43,7 +43,7 @@ repositories {
url "$rootDir/../node_modules/jsc-android/dist"
}
google()
mavencentral()
mavenCentral()
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Expand Down Expand Up @@ -58,86 +59,53 @@ public void openContactSelection(Promise contactsPromise) {
*/
private void launchPicker(Promise contactsPromise, int requestCode) {
mContactsPromise = contactsPromise;
Cursor cursor = this.contentResolver.query(Contacts.CONTENT_URI, null, null, null, null);

if (cursor != null) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(Contacts.CONTENT_TYPE);

Intent intent = new Intent(Intent.ACTION_PICK,ContactsContract.CommonDataKinds.Phone.CONTENT_URI);
Activity activity = getCurrentActivity();
if (intent.resolveActivity(activity.getPackageManager()) != null) {
activity.startActivityForResult(intent, requestCode);
}
cursor.close();
} else {
mContactsPromise.reject(E_CONTACT_PERMISSION, "no permission");
}

}

@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
if (mContactsPromise == null || requestCode != CONTACT_REQUEST) {
return;
}

//Request was cancelled
if (resultCode != Activity.RESULT_OK) {
mContactsPromise.reject(E_CONTACT_CANCELLED, "Cancelled");
return;
}

// Retrieve all possible data about contact and return as a JS object
}
WritableMap contactData = Arguments.createMap();

try {
String id = getContactId(intent.getData());
contactData.putString("recordId", id);
Uri contactUri = buildContactUri(id);

boolean foundData = false;

WritableArray phones = Arguments.createArray();
WritableArray emails = Arguments.createArray();
WritableArray postalAddresses = Arguments.createArray();

Cursor cursor = openContactQuery(contactUri);
if (cursor.moveToFirst()) {
do {
String mime = cursor.getString(cursor.getColumnIndex(Entity.MIMETYPE));
switch (mime) {
case StructuredName.CONTENT_ITEM_TYPE:
addNameData(contactData, cursor);
foundData = true;
break;

case StructuredPostal.CONTENT_ITEM_TYPE:
addPostalData(postalAddresses, cursor, activity);
foundData = true;
break;

case Phone.CONTENT_ITEM_TYPE:
addPhoneEntry(phones, cursor, activity);
foundData = true;
break;

case Email.CONTENT_ITEM_TYPE:
addEmailEntry(emails, cursor, activity);
foundData = true;
break;
}
} while (cursor.moveToNext());
Cursor cursor = activity.getContentResolver().query(intent.getData(), null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {

int any=cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
String number = cursor.getString(any);
String fullName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
foundData= true;
addPhoneEntry(phones, cursor, activity);
contactData.putArray("phones", phones);
contactData.putString("phone", number);
contactData.putString("name", fullName);

}
cursor.close();

contactData.putArray("phones", phones);
contactData.putArray("emails", emails);
contactData.putArray("postalAddresses", postalAddresses);
cursor.close();

if (foundData) {
mContactsPromise.resolve(contactData);
} else {
mContactsPromise.reject(E_CONTACT_NO_DATA, "No data found for contact");
}
} catch (SelectContactException e) {
mContactsPromise.reject(E_CONTACT_EXCEPTION, e.getMessage());
}
} catch (Exception e) {
Log.e(TAG, "Unexpected exception reading from contacts", e);
mContactsPromise.reject(E_CONTACT_EXCEPTION, e.getMessage());
Expand Down
Loading