diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
new file mode 100644
index 0000000..2a4766d
--- /dev/null
+++ b/.github/workflows/npm-publish.yml
@@ -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}}
diff --git a/README.md b/README.md
index c6d7231..c2b45c2 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -38,10 +46,13 @@ For Android support, make sure your manifest file includes permission to read co
```
+
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
@@ -67,12 +78,12 @@ selectContactEmail(): Promise;
selectContactPostalAddress(): Promise;
```
-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
@@ -84,6 +95,7 @@ yarn add react-native-action-sheet
```
For React Native => 0.59 only:
+
```
react-native link react-native-action-sheet
```
@@ -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;
}
-
-
```
diff --git a/android/.gitignore b/android/.gitignore
deleted file mode 100644
index 635ae57..0000000
--- a/android/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.gradle
-local.properties
\ No newline at end of file
diff --git a/android/build.gradle b/android/build.gradle
index 1a1583f..a7cd356 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -9,7 +9,7 @@ buildscript {
if (project == rootProject) {
repositories {
google()
- mavencentral()
+ mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
@@ -43,7 +43,7 @@ repositories {
url "$rootDir/../node_modules/jsc-android/dist"
}
google()
- mavencentral()
+ mavenCentral()
}
dependencies {
diff --git a/android/src/main/java/com/streem/selectcontact/SelectContactModule.java b/android/src/main/java/com/streem/selectcontact/SelectContactModule.java
index f708890..18117bf 100644
--- a/android/src/main/java/com/streem/selectcontact/SelectContactModule.java
+++ b/android/src/main/java/com/streem/selectcontact/SelectContactModule.java
@@ -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;
@@ -58,19 +59,13 @@ 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
@@ -78,66 +73,39 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
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());
diff --git a/index.js b/index.js
index b740605..7a01bf9 100644
--- a/index.js
+++ b/index.js
@@ -1,158 +1,148 @@
-'use strict';
+"use strict";
-import {
- Alert,
- ActionSheetIOS,
- NativeModules,
- Platform
-} from 'react-native';
+import { Alert, ActionSheetIOS, NativeModules, Platform } from "react-native";
const { SelectContact, ActionSheetAndroid } = NativeModules;
const ActionSheet = Platform.select({
- ios: ActionSheetIOS,
- android: ActionSheetAndroid
+ ios: ActionSheetIOS,
+ android: ActionSheetAndroid,
});
-const DEFAULT_TEXT_OPTIONS = {
- cancel: "Cancel",
- selectPhone: "Select Phone",
- selectPostalAddress: "Select Postal Address",
- selectEmail: "Select Email",
- errorNoPhoneNumbersTitle: "No Phone Numbers",
- errorNoPhoneNumbersBody: "We could not find any phone numbers for ",
- errorNoAddressTitle: "No Postal Addresses",
- errorNoAddressBody: "We could not find any postal addresses for ",
- errorNoEmailTitle: "No Email Addresses",
- errorNoEmailBody: "We could not find any email addresses for ",
- errorOpenTwice: "Cannot open the contact selector twice",
-}
-
let currentlyOpen = false;
const SelectContactApi = {
+ selectContact() {
+ if (currentlyOpen) {
+ if (Platform.OS === "ios") {
+ //这里再iOS 18的时候可能会出现无响应的时候点击再弹即可
+ currentlyOpen = false;
+ }
+ return Promise.reject(
+ new Error("Cannot open the contact selector twice")
+ );
+ }
- selectContact(textOptions=DEFAULT_TEXT_OPTIONS) {
- if (currentlyOpen) {
- return Promise.reject(new Error(textOptions.errorOpenTwice));
- }
+ currentlyOpen = true;
- currentlyOpen = true;
-
- return SelectContact.openContactSelection()
- .then(contact => {
- currentlyOpen = false;
- return contact;
- })
- .catch(err => {
- currentlyOpen = false;
-
- // Resolve to null when cancelled
- if (err.code === 'E_CONTACT_CANCELLED') {
- return null;
- }
-
- throw err;
- });
- },
-
- selectContactPostalAddress(textOptions=DEFAULT_TEXT_OPTIONS) {
- return SelectContactApi.selectContact(textOptions)
- .then(contact => {
- if (!contact) {
- return null;
- }
-
- let addresses = contact && contact.postalAddresses || [];
- if (addresses.length === 0) {
- Alert.alert(textOptions.errorNoAddressTitle, `${textOptions.errorNoAddressBody}${contact.name}`);
- return null;
- }
-
- return selectPostalAddress(addresses)
- .then(selectedAddress => {
- return selectedAddress ? { contact, selectedAddress } : null;
- });
- })
- },
-
- selectContactPhone(textOptions=DEFAULT_TEXT_OPTIONS) {
- return SelectContactApi.selectContact(textOptions)
- .then(contact => {
- if (!contact) {
- return null;
- }
-
- let phones = contact && contact.phones || [];
- if (phones.length === 0) {
- Alert.alert(textOptions.errorNoPhoneNumbersTitle, `${textOptions.errorNoPhoneNumbersBody}${contact.name}`);
- return null;
- }
-
- return selectPhone(phones, textOptions)
- .then(selectedPhone => {
- return selectedPhone ? { contact, selectedPhone } : null;
- });
- });
- },
-
- selectContactEmail(textOptions=DEFAULT_TEXT_OPTIONS) {
- return SelectContactApi.selectContact(textOptions)
- .then(contact => {
- if (!contact) {
- return null;
- }
-
- let emails = contact && contact.emails || [];
- if (emails.length === 0) {
- Alert.alert(textOptions.errorNoEmailTitle, `${textOptions.errorNoEmailBody}${contact.name}`);
- return null;
- }
-
- return selectEmail(emails)
- .then(selectedEmail => {
- return selectedEmail ? { contact, selectedEmail } : null;
- });
- });
- }
+ return SelectContact.openContactSelection()
+ .then((contact) => {
+ currentlyOpen = false;
+ return contact;
+ })
+ .catch((err) => {
+ currentlyOpen = false;
+
+ // Resolve to null when cancelled
+ if (err.code === "E_CONTACT_CANCELLED") {
+ return null;
+ }
+ throw err;
+ });
+ },
+
+ selectContactPostalAddress() {
+ return SelectContactApi.selectContact().then((contact) => {
+ if (!contact) {
+ return null;
+ }
+
+ let addresses = (contact && contact.postalAddresses) || [];
+ if (addresses.length === 0) {
+ Alert.alert(
+ "No Postal Addresses",
+ `We could not find any postal addresses for ${contact.name}`
+ );
+ return null;
+ }
+
+ return selectPostalAddress(addresses).then((selectedAddress) => {
+ return selectedAddress ? { contact, selectedAddress } : null;
+ });
+ });
+ },
+
+ selectContactPhone() {
+ return SelectContactApi.selectContact().then((contact) => {
+ if (!contact) {
+ return null;
+ }
+
+ let phones = (contact && contact.phones) || [];
+ if (phones.length === 0) {
+ Alert.alert(
+ "No Phone Numbers",
+ `We could not find any phone numbers for ${contact.name}`
+ );
+ return null;
+ }
+
+ return selectPhone(phones).then((selectedPhone) => {
+ return selectedPhone ? { contact, selectedPhone } : null;
+ });
+ });
+ },
+
+ selectContactEmail() {
+ return SelectContactApi.selectContact().then((contact) => {
+ if (!contact) {
+ return null;
+ }
+
+ let emails = (contact && contact.emails) || [];
+ if (emails.length === 0) {
+ Alert.alert(
+ "No Email Addresses",
+ `We could not find any email addresses for ${contact.name}`
+ );
+ return null;
+ }
+
+ return selectEmail(emails).then((selectedEmail) => {
+ return selectedEmail ? { contact, selectedEmail } : null;
+ });
+ });
+ },
};
module.exports = SelectContactApi;
+function selectPhone(phones) {
+ if (phones.length < 2 || !ActionSheet) {
+ return Promise.resolve(phones[0]);
+ }
-function selectPhone(phones, textOptions) {
- if (phones.length < 2 || !ActionSheet) {
- return Promise.resolve(phones[0]);
- }
-
- let options = phones.map(phone => {
- let { number, type } = phone;
- return number + (type ? ` - ${type}` : '');
- });
+ let options = phones.map((phone) => {
+ let { number, type } = phone;
+ return number + (type ? ` - ${type}` : "");
+ });
- if (Platform.OS === 'ios') {
- options.push(textOptions.cancel);
- }
+ if (Platform.OS === "ios") {
+ options.push("Cancel");
+ }
- return new Promise(((resolve) => {
- ActionSheet.showActionSheetWithOptions({
- title: textOptions.selectPhone,
- options: options,
- cancelButtonIndex: options.length - 1,
- },
- (buttonIndex) => {
- resolve(phones[buttonIndex]);
- });
- }));
+ return new Promise((resolve) => {
+ ActionSheet.showActionSheetWithOptions(
+ {
+ title: "Select Phone",
+ options: options,
+ cancelButtonIndex: options.length - 1,
+ },
+ (buttonIndex) => {
+ resolve(phones[buttonIndex]);
+ }
+ );
+ });
}
-function selectPostalAddress(addresses, textOptions) {
+function selectPostalAddress(addresses) {
if (addresses.length < 2 || !ActionSheet) {
return Promise.resolve(addresses[0]);
}
- let options = addresses.map(address => {
- let { formattedAddress, street, city, state, postalCode, isoCountryCode } = address;
+ let options = addresses.map((address) => {
+ let { formattedAddress, street, city, state, postalCode, isoCountryCode } =
+ address;
if (formattedAddress) {
return formattedAddress;
@@ -161,44 +151,48 @@ function selectPostalAddress(addresses, textOptions) {
return `${street} ${city}, ${state} ${postalCode} ${isoCountryCode}`;
});
- if (Platform.OS === 'ios') {
- options.push(textOptions.cancel);
+ if (Platform.OS === "ios") {
+ options.push("Cancel");
}
- return new Promise(((resolve) => {
- ActionSheet.showActionSheetWithOptions({
- title: textOptions.selectPostalAddress,
- options: options,
- cancelButtonIndex: options.length - 1,
- },
- (buttonIndex) => {
- resolve(addresses[buttonIndex]);
- });
- }));
+ return new Promise((resolve) => {
+ ActionSheet.showActionSheetWithOptions(
+ {
+ title: "Select Postal Address",
+ options: options,
+ cancelButtonIndex: options.length - 1,
+ },
+ (buttonIndex) => {
+ resolve(addresses[buttonIndex]);
+ }
+ );
+ });
}
function selectEmail(emails) {
- if (emails.length < 2 || !ActionSheet) {
- return Promise.resolve(emails[0]);
- }
+ if (emails.length < 2 || !ActionSheet) {
+ return Promise.resolve(emails[0]);
+ }
- let options = emails.map(email => {
- let { address, type } = email;
- return address + (type ? ` - ${type}` : '');
- });
+ let options = emails.map((email) => {
+ let { address, type } = email;
+ return address + (type ? ` - ${type}` : "");
+ });
- if (Platform.OS === 'ios') {
- options.push(textOptions.cancel);
- }
+ if (Platform.OS === "ios") {
+ options.push("Cancel");
+ }
- return new Promise(((resolve) => {
- ActionSheet.showActionSheetWithOptions({
- title: textOptions.selectEmail,
- options: options,
- cancelButtonIndex: options.length - 1,
- },
- (buttonIndex) => {
- resolve(emails[buttonIndex]);
- });
- }));
+ return new Promise((resolve) => {
+ ActionSheet.showActionSheetWithOptions(
+ {
+ title: "Select Email",
+ options: options,
+ cancelButtonIndex: options.length - 1,
+ },
+ (buttonIndex) => {
+ resolve(emails[buttonIndex]);
+ }
+ );
+ });
}
diff --git a/package.json b/package.json
index f443b26..60932d4 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
- "name": "react-native-select-contact",
+ "name": "@chen834921478/react-native-select-contact",
"description": "A react native company to select a contact from your phone's contact list",
- "version": "1.6.3",
+ "version": "1.7.1",
"nativePackage": true,
"typings": "./selectContact.d.ts",
"scripts": {
@@ -9,7 +9,7 @@
},
"repository": {
"type": "git",
- "url": "git+https://github.com/streem/react-native-select-contact.git"
+ "url": "git+https://github.com/chen834921478/react-native-select-contact.git"
},
"keywords": [
"react",
@@ -20,13 +20,13 @@
"select contact"
],
"author": {
- "name": "Streem (originally LynxIT iDigital)"
+ "name": "chen834921478 (originally LynxIT iDigital)"
},
"license": "MIT",
"bugs": {
- "url": "https://github.com/streem/react-native-select-contact/issues"
+ "url": "https://github.com/chen834921478/react-native-select-contact/issues"
},
- "homepage": "https://github.com/streem/react-native-select-contact",
+ "homepage": "https://github.com/chen834921478/react-native-select-contact",
"maintainers": [
{
"name": "Sean Adkinson"
diff --git a/selectContact.d.ts b/selectContact.d.ts
index 688f9dd..a8cade6 100644
--- a/selectContact.d.ts
+++ b/selectContact.d.ts
@@ -1,8 +1,8 @@
-export function selectContact(textOptions? : TextOptions): Promise;
-export function selectContactPhone(textOptions? : TextOptions): Promise;
-export function selectContactEmail(textOptions? : TextOptions): Promise;
-export function selectContactPostalAddress(textOptions? : TextOptions): Promise;
+export function selectContact(): Promise;
+export function selectContactPhone(): Promise;
+export function selectContactEmail(): Promise;
+export function selectContactPostalAddress(): Promise;
export interface PhoneEntry {
number: string,
@@ -45,18 +45,4 @@ export interface ContactEmailSelection {
export interface ContactPostalAddressSelection {
contact: Contact,
selectedAddress: AddressEntry
-}
-
-export interface TextOptions {
- cancel: String,
- selectPhone: String,
- selectPostalAddress: String,
- selectEmail: String,
- errorNoPhoneNumbersTitle: String,
- errorNoPhoneNumbersBody: String,
- errorNoAddressTitle: String,
- errorNoAddressBody: String,
- errorNoEmailTitle: String,
- errorNoEmailBody: String,
- errorOpenTwice: String,
}
\ No newline at end of file