@capgo/capacitor-contacts 8.0.19 → 8.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -24
- package/android/src/main/java/app/capgo/contacts/CapacitorContactsPlugin.java +231 -20
- package/dist/docs.json +71 -5
- package/dist/esm/definitions.d.ts +70 -0
- package/dist/esm/definitions.js +29 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/plugin.cjs.js +30 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +30 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorContactsPlugin/CapacitorContactsPlugin.swift +190 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,50 @@ npm install @capgo/capacitor-contacts
|
|
|
45
45
|
npx cap sync
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
## Android
|
|
49
|
+
|
|
50
|
+
Google Play's Contacts Permissions policy (effective 27 January 2027) allows `READ_CONTACTS` only when the Android Contact Picker cannot cover your core feature, for apps that target Android 17 (API 37) or later.
|
|
51
|
+
|
|
52
|
+
This plugin does **not** declare `READ_CONTACTS` or `WRITE_CONTACTS`. Add them in your app manifest only if you need them.
|
|
53
|
+
|
|
54
|
+
**Pick a phone, email, or address without `READ_CONTACTS`:**
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { CapacitorContacts, ContactProperty } from '@capgo/capacitor-contacts';
|
|
58
|
+
|
|
59
|
+
const { contacts } = await CapacitorContacts.pickContacts({
|
|
60
|
+
property: ContactProperty.PhoneNumber,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const number = contacts[0]?.phoneNumbers?.[0]?.value;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`property` works on every Android version. The picker returns a data-row URI the plugin can read without a permission. The result contains `id`, `displayName`, and the selected property only.
|
|
67
|
+
|
|
68
|
+
Use `ContactProperty.EmailAddress` or `ContactProperty.PostalAddress` the same way.
|
|
69
|
+
|
|
70
|
+
**When you still need `READ_CONTACTS`:**
|
|
71
|
+
|
|
72
|
+
- `pickContacts()` / `pickContact()` without `property` (full contact record)
|
|
73
|
+
- `getContacts()`, `getContactById()`, `countContacts()`, `getGroups()`, `getAccounts()`
|
|
74
|
+
- Syncing, backing up, or matching the whole address book
|
|
75
|
+
|
|
76
|
+
Those APIs keep working as before once `READ_CONTACTS` is in the app manifest and granted. Pass `property` only if you want to drop the permission.
|
|
77
|
+
|
|
78
|
+
If full-address-book access is a core feature, add the permission and file the Play Console declaration before pre-review checks start on 27 October 2026:
|
|
79
|
+
|
|
80
|
+
```xml
|
|
81
|
+
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`WRITE_CONTACTS` is unchanged by this policy. Add it only if you create or update contacts in code.
|
|
85
|
+
|
|
86
|
+
Without `READ_CONTACTS`, `pickContacts()` without `property` can still return `id` and `displayName` (and, on Android 17+, picker-granted fields). Use `property` to get a phone, email, or address with no permission on every Android version.
|
|
87
|
+
|
|
88
|
+
## iOS
|
|
89
|
+
|
|
90
|
+
`pickContact()` / `pickContacts()` never require a contacts permission. Add `NSContactsUsageDescription` only if you call methods that read or write the address book (`getContacts`, `createContact`, and similar).
|
|
91
|
+
|
|
48
92
|
## API
|
|
49
93
|
|
|
50
94
|
<docgen-index>
|
|
@@ -73,6 +117,7 @@ npx cap sync
|
|
|
73
117
|
* [`getPluginVersion()`](#getpluginversion)
|
|
74
118
|
* [Interfaces](#interfaces)
|
|
75
119
|
* [Type Aliases](#type-aliases)
|
|
120
|
+
* [Enums](#enums)
|
|
76
121
|
|
|
77
122
|
</docgen-index>
|
|
78
123
|
|
|
@@ -359,6 +404,9 @@ pickContact(options?: PickContactsOptions | undefined) => Promise<PickContactRes
|
|
|
359
404
|
|
|
360
405
|
<a href="#pick">Pick</a> a single contact using the native contact picker.
|
|
361
406
|
|
|
407
|
+
Same options as {@link pickContacts}. Without `property`, Android still
|
|
408
|
+
returns the full contact when `READ_CONTACTS` is granted.
|
|
409
|
+
|
|
362
410
|
| Param | Type | Description |
|
|
363
411
|
| ------------- | ------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
364
412
|
| **`options`** | <code><a href="#pickcontactsoptions">PickContactsOptions</a></code> | - Optional fields to retrieve and picker configuration |
|
|
@@ -378,6 +426,14 @@ pickContacts(options?: PickContactsOptions | undefined) => Promise<PickContactsR
|
|
|
378
426
|
|
|
379
427
|
<a href="#pick">Pick</a> one or more contacts using the native contact picker.
|
|
380
428
|
|
|
429
|
+
On iOS this never requires a contacts permission.
|
|
430
|
+
|
|
431
|
+
On Android, `pickContacts()` without `property` still returns the full
|
|
432
|
+
contact when the app has `READ_CONTACTS`. Pass `property` only when you
|
|
433
|
+
want a single phone number, email address, or postal address and can omit
|
|
434
|
+
that permission (required to target Android 17 / API 37 without a Play
|
|
435
|
+
Console contacts declaration).
|
|
436
|
+
|
|
381
437
|
| Param | Type | Description |
|
|
382
438
|
| ------------- | ------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
383
439
|
| **`options`** | <code><a href="#pickcontactsoptions">PickContactsOptions</a></code> | - Optional fields to retrieve and picker configuration |
|
|
@@ -489,26 +545,27 @@ Options for creating a contact.
|
|
|
489
545
|
|
|
490
546
|
<a href="#contact">Contact</a> information.
|
|
491
547
|
|
|
492
|
-
| Prop | Type | Description
|
|
493
|
-
| ---------------------- | --------------------------------------------- |
|
|
494
|
-
| **`id`** | <code>string</code> | Unique identifier for the contact.
|
|
495
|
-
| **`
|
|
496
|
-
| **`
|
|
497
|
-
| **`
|
|
498
|
-
| **`
|
|
499
|
-
| **`
|
|
500
|
-
| **`
|
|
501
|
-
| **`
|
|
502
|
-
| **`
|
|
503
|
-
| **`
|
|
504
|
-
| **`
|
|
505
|
-
| **`
|
|
506
|
-
| **`
|
|
507
|
-
| **`
|
|
508
|
-
| **`
|
|
509
|
-
| **`
|
|
510
|
-
| **`
|
|
511
|
-
| **`
|
|
548
|
+
| Prop | Type | Description | Since |
|
|
549
|
+
| ---------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
550
|
+
| **`id`** | <code>string</code> | Unique identifier for the contact. | 1.0.0 |
|
|
551
|
+
| **`displayName`** | <code>string</code> | Formatted name shown by the device for this contact. Derived from `CNContactFormatter` on iOS and `DISPLAY_NAME` on Android. Read-only: setting this when creating or updating a contact has no effect. Use `givenName` and `familyName` to write a name. | 8.1.0 |
|
|
552
|
+
| **`account`** | <code><a href="#account">Account</a></code> | <a href="#account">Account</a> information for the contact. | 1.0.0 |
|
|
553
|
+
| **`birthday`** | <code><a href="#birthday">Birthday</a></code> | <a href="#birthday">Birthday</a> information for the contact. | 1.0.0 |
|
|
554
|
+
| **`emailAddresses`** | <code>EmailAddress[]</code> | Email addresses for the contact. | 1.0.0 |
|
|
555
|
+
| **`familyName`** | <code>string</code> | Family name (last name) of the contact. | 1.0.0 |
|
|
556
|
+
| **`fullName`** | <code>string</code> | Full name of the contact. | 1.0.0 |
|
|
557
|
+
| **`givenName`** | <code>string</code> | Given name (first name) of the contact. | 1.0.0 |
|
|
558
|
+
| **`groupIds`** | <code>string[]</code> | <a href="#group">Group</a> IDs the contact belongs to. | 1.0.0 |
|
|
559
|
+
| **`jobTitle`** | <code>string</code> | Job title of the contact. | 1.0.0 |
|
|
560
|
+
| **`middleName`** | <code>string</code> | Middle name of the contact. | 1.0.0 |
|
|
561
|
+
| **`namePrefix`** | <code>string</code> | Name prefix (e.g., "Dr.", "Mr.", "Ms.") of the contact. | 1.0.0 |
|
|
562
|
+
| **`nameSuffix`** | <code>string</code> | Name suffix (e.g., "Jr.", "Sr.", "III") of the contact. | 1.0.0 |
|
|
563
|
+
| **`note`** | <code>string</code> | Notes about the contact. | 1.0.0 |
|
|
564
|
+
| **`organizationName`** | <code>string</code> | Organization name of the contact. | 1.0.0 |
|
|
565
|
+
| **`phoneNumbers`** | <code>PhoneNumber[]</code> | Phone numbers for the contact. | 1.0.0 |
|
|
566
|
+
| **`photo`** | <code>string</code> | Base64-encoded photo of the contact. | 1.0.0 |
|
|
567
|
+
| **`postalAddresses`** | <code>PostalAddress[]</code> | Postal addresses for the contact. | 1.0.0 |
|
|
568
|
+
| **`urlAddresses`** | <code>UrlAddress[]</code> | URL addresses for the contact. | 1.0.0 |
|
|
512
569
|
|
|
513
570
|
|
|
514
571
|
#### Account
|
|
@@ -774,10 +831,11 @@ Result from picking contacts.
|
|
|
774
831
|
|
|
775
832
|
Options for picking contacts using the native contact picker.
|
|
776
833
|
|
|
777
|
-
| Prop | Type
|
|
778
|
-
| -------------- |
|
|
779
|
-
| **`fields`** | <code>(keyof <a href="#contact">Contact</a>)[]</code>
|
|
780
|
-
| **`multiple`** | <code>boolean</code>
|
|
834
|
+
| Prop | Type | Description | Since |
|
|
835
|
+
| -------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
836
|
+
| **`fields`** | <code>(keyof <a href="#contact">Contact</a>)[]</code> | Optional list of specific fields to retrieve. If not specified, all fields are returned. Ignored when {@link <a href="#pickcontactsoptions">PickContactsOptions.property</a>} is set. The result then contains only `id`, `displayName`, and the selected property. | 1.0.0 |
|
|
837
|
+
| **`multiple`** | <code>boolean</code> | Whether to allow selecting multiple contacts. Default is false. Ignored when {@link <a href="#pickcontactsoptions">PickContactsOptions.property</a>} is set. Property picking always returns a single value. | 1.0.0 |
|
|
838
|
+
| **`property`** | <code><a href="#contactproperty">ContactProperty</a></code> | Restrict the picker to a single contact property. On Android this launches the picker against the phone, email, or postal address table and reads the granted data row. That requires no `READ_CONTACTS` permission on any Android version, which is required for apps that only pick a contact detail and target Android 17 (API 37) or later under the Google Play Contacts Permissions policy. On iOS the picker lets the user choose one phone number, email address, or postal address. It never requires a contacts permission. | 8.1.0 |
|
|
781
839
|
|
|
782
840
|
|
|
783
841
|
#### UpdateContactByIdOptions
|
|
@@ -900,6 +958,18 @@ Type of contacts permission to request.
|
|
|
900
958
|
|
|
901
959
|
<code>'readContacts' | 'writeContacts'</code>
|
|
902
960
|
|
|
961
|
+
|
|
962
|
+
### Enums
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
#### ContactProperty
|
|
966
|
+
|
|
967
|
+
| Members | Value | Description | Since |
|
|
968
|
+
| ------------------- | ---------------------------- | ------------------------ | ----- |
|
|
969
|
+
| **`PhoneNumber`** | <code>'phoneNumber'</code> | A single phone number. | 8.1.0 |
|
|
970
|
+
| **`EmailAddress`** | <code>'emailAddress'</code> | A single email address. | 8.1.0 |
|
|
971
|
+
| **`PostalAddress`** | <code>'postalAddress'</code> | A single postal address. | 8.1.0 |
|
|
972
|
+
|
|
903
973
|
</docgen-api>
|
|
904
974
|
|
|
905
975
|
### Credit
|
|
@@ -40,7 +40,7 @@ import java.util.Set;
|
|
|
40
40
|
)
|
|
41
41
|
public class CapacitorContactsPlugin extends Plugin {
|
|
42
42
|
|
|
43
|
-
private final String pluginVersion = "8.
|
|
43
|
+
private final String pluginVersion = "8.1.2";
|
|
44
44
|
private static final int BATCH_SIZE = 50;
|
|
45
45
|
|
|
46
46
|
// MARK: - Implemented API surface
|
|
@@ -467,19 +467,48 @@ public class CapacitorContactsPlugin extends Plugin {
|
|
|
467
467
|
private static final int EDIT_CONTACT_REQUEST = 7004;
|
|
468
468
|
|
|
469
469
|
private PluginCall currentPickerCall;
|
|
470
|
+
private String currentPickerProperty;
|
|
470
471
|
|
|
471
472
|
@PluginMethod
|
|
472
473
|
public void pickContact(PluginCall call) {
|
|
473
|
-
|
|
474
|
-
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
|
|
475
|
-
startActivityForResult(call, intent, PICK_CONTACT_REQUEST);
|
|
474
|
+
launchContactPicker(call);
|
|
476
475
|
}
|
|
477
476
|
|
|
478
477
|
@PluginMethod
|
|
479
478
|
public void pickContacts(PluginCall call) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
479
|
+
launchContactPicker(call);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private void launchContactPicker(PluginCall call) {
|
|
483
|
+
String property = call.getString("property");
|
|
484
|
+
if (property != null && contentUriForProperty(property) == null) {
|
|
485
|
+
call.reject("Invalid property. Use phoneNumber, emailAddress, or postalAddress.");
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
currentPickerCall = call;
|
|
490
|
+
currentPickerProperty = property;
|
|
491
|
+
startActivityForResult(call, createPickIntent(property), PICK_CONTACT_REQUEST);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private Intent createPickIntent(String property) {
|
|
495
|
+
if (property != null) {
|
|
496
|
+
return new Intent(Intent.ACTION_PICK, contentUriForProperty(property));
|
|
497
|
+
}
|
|
498
|
+
return new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private Uri contentUriForProperty(String property) {
|
|
502
|
+
if ("phoneNumber".equals(property)) {
|
|
503
|
+
return ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
|
|
504
|
+
}
|
|
505
|
+
if ("emailAddress".equals(property)) {
|
|
506
|
+
return ContactsContract.CommonDataKinds.Email.CONTENT_URI;
|
|
507
|
+
}
|
|
508
|
+
if ("postalAddress".equals(property)) {
|
|
509
|
+
return ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI;
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
483
512
|
}
|
|
484
513
|
|
|
485
514
|
@PluginMethod
|
|
@@ -546,6 +575,7 @@ public class CapacitorContactsPlugin extends Plugin {
|
|
|
546
575
|
currentPickerCall = null;
|
|
547
576
|
|
|
548
577
|
if (resultCode != android.app.Activity.RESULT_OK) {
|
|
578
|
+
currentPickerProperty = null;
|
|
549
579
|
if (requestCode == PICK_CONTACT_REQUEST) {
|
|
550
580
|
call.resolve(new JSObject().put("contacts", new JSArray()));
|
|
551
581
|
} else if (requestCode == CREATE_CONTACT_REQUEST) {
|
|
@@ -557,20 +587,9 @@ public class CapacitorContactsPlugin extends Plugin {
|
|
|
557
587
|
try {
|
|
558
588
|
if (requestCode == PICK_CONTACT_REQUEST) {
|
|
559
589
|
if (data != null && data.getData() != null) {
|
|
560
|
-
|
|
561
|
-
if (contactId != null) {
|
|
562
|
-
ContactBuilder builder = fetchContact(contactId, null);
|
|
563
|
-
if (builder != null) {
|
|
564
|
-
JSArray contacts = new JSArray();
|
|
565
|
-
contacts.put(builder.toJSObject(null));
|
|
566
|
-
call.resolve(new JSObject().put("contacts", contacts));
|
|
567
|
-
} else {
|
|
568
|
-
call.resolve(new JSObject().put("contacts", new JSArray()));
|
|
569
|
-
}
|
|
570
|
-
} else {
|
|
571
|
-
call.resolve(new JSObject().put("contacts", new JSArray()));
|
|
572
|
-
}
|
|
590
|
+
resolvePickedUri(call, data.getData());
|
|
573
591
|
} else {
|
|
592
|
+
currentPickerProperty = null;
|
|
574
593
|
call.resolve(new JSObject().put("contacts", new JSArray()));
|
|
575
594
|
}
|
|
576
595
|
} else if (requestCode == CREATE_CONTACT_REQUEST) {
|
|
@@ -590,6 +609,192 @@ public class CapacitorContactsPlugin extends Plugin {
|
|
|
590
609
|
}
|
|
591
610
|
}
|
|
592
611
|
|
|
612
|
+
private void resolvePickedUri(PluginCall call, Uri uri) {
|
|
613
|
+
String property = currentPickerProperty;
|
|
614
|
+
currentPickerProperty = null;
|
|
615
|
+
|
|
616
|
+
if (property != null) {
|
|
617
|
+
JSObject contact = contactFromDataRowUri(uri, property);
|
|
618
|
+
JSArray contacts = new JSArray();
|
|
619
|
+
if (contact != null) {
|
|
620
|
+
contacts.put(contact);
|
|
621
|
+
}
|
|
622
|
+
call.resolve(new JSObject().put("contacts", contacts));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
call.resolve(new JSObject().put("contacts", contactsFromFullPick(uri, fieldsForPickedContact(call))));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
private Set<String> fieldsForPickedContact(PluginCall call) {
|
|
630
|
+
Set<String> fields = parseFieldsArray(call);
|
|
631
|
+
if (fields == null) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
Set<String> withIdentity = new HashSet<>(fields);
|
|
635
|
+
withIdentity.add("id");
|
|
636
|
+
withIdentity.add("displayName");
|
|
637
|
+
return withIdentity;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private boolean isSessionUri(Uri uri) {
|
|
641
|
+
String authority = uri.getAuthority();
|
|
642
|
+
return authority != null && authority.contains("picker");
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private JSArray contactsFromFullPick(Uri uri, Set<String> fields) {
|
|
646
|
+
JSArray contacts = new JSArray();
|
|
647
|
+
List<String> ids = contactIdsFromPickerUri(uri);
|
|
648
|
+
|
|
649
|
+
if (hasReadPermission()) {
|
|
650
|
+
for (String contactId : ids) {
|
|
651
|
+
try {
|
|
652
|
+
ContactBuilder builder = fetchContact(contactId, fields);
|
|
653
|
+
if (builder != null) {
|
|
654
|
+
if (builder.displayName == null) {
|
|
655
|
+
builder.displayName = builder.fullName;
|
|
656
|
+
}
|
|
657
|
+
contacts.put(builder.toJSObject(fields));
|
|
658
|
+
}
|
|
659
|
+
} catch (Exception ex) {
|
|
660
|
+
android.util.Log.w("CapacitorContacts", "Failed to fetch picked contact", ex);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (contacts.length() > 0) {
|
|
666
|
+
return contacts;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (isSessionUri(uri)) {
|
|
670
|
+
return contactsFromSessionUri(uri, fields);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (!ids.isEmpty()) {
|
|
674
|
+
ContactBuilder builder = new ContactBuilder(ids.get(0));
|
|
675
|
+
String displayName = resolveDisplayNameFromUri(uri);
|
|
676
|
+
builder.displayName = displayName;
|
|
677
|
+
builder.fullName = displayName;
|
|
678
|
+
Set<String> thinFields = new HashSet<>();
|
|
679
|
+
thinFields.add("id");
|
|
680
|
+
thinFields.add("displayName");
|
|
681
|
+
contacts.put(builder.toJSObject(thinFields));
|
|
682
|
+
}
|
|
683
|
+
return contacts;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
private List<String> contactIdsFromPickerUri(Uri uri) {
|
|
687
|
+
List<String> ids = new ArrayList<>();
|
|
688
|
+
if (isSessionUri(uri)) {
|
|
689
|
+
Set<String> seen = new HashSet<>();
|
|
690
|
+
ContentResolver resolver = getContext().getContentResolver();
|
|
691
|
+
try (Cursor cursor = resolver.query(uri, null, null, null, null)) {
|
|
692
|
+
if (cursor != null) {
|
|
693
|
+
while (cursor.moveToNext()) {
|
|
694
|
+
String contactId = getStringColumn(cursor, ContactsContract.Data.CONTACT_ID);
|
|
695
|
+
if (contactId != null && seen.add(contactId)) {
|
|
696
|
+
ids.add(contactId);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
} catch (Exception ex) {
|
|
701
|
+
android.util.Log.w("CapacitorContacts", "Failed to read picker session contact ids", ex);
|
|
702
|
+
}
|
|
703
|
+
return ids;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
String contactId = getContactIdFromUri(uri);
|
|
707
|
+
if (contactId != null) {
|
|
708
|
+
ids.add(contactId);
|
|
709
|
+
}
|
|
710
|
+
return ids;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private JSArray contactsFromSessionUri(Uri sessionUri, Set<String> fields) {
|
|
714
|
+
JSArray contacts = new JSArray();
|
|
715
|
+
Map<String, ContactBuilder> builders = new java.util.LinkedHashMap<>();
|
|
716
|
+
ContentResolver resolver = getContext().getContentResolver();
|
|
717
|
+
|
|
718
|
+
try (Cursor cursor = resolver.query(sessionUri, null, null, null, null)) {
|
|
719
|
+
if (cursor == null) {
|
|
720
|
+
return contacts;
|
|
721
|
+
}
|
|
722
|
+
while (cursor.moveToNext()) {
|
|
723
|
+
String contactId = getStringColumn(cursor, ContactsContract.Data.CONTACT_ID);
|
|
724
|
+
if (contactId == null) {
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
ContactBuilder builder = builders.get(contactId);
|
|
728
|
+
if (builder == null) {
|
|
729
|
+
builder = new ContactBuilder(contactId);
|
|
730
|
+
String displayName = getStringColumn(cursor, ContactsContract.Data.DISPLAY_NAME);
|
|
731
|
+
builder.displayName = displayName;
|
|
732
|
+
builder.fullName = displayName;
|
|
733
|
+
builders.put(contactId, builder);
|
|
734
|
+
}
|
|
735
|
+
processDataRow(builder, cursor);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
for (ContactBuilder builder : builders.values()) {
|
|
740
|
+
contacts.put(builder.toJSObject(fields));
|
|
741
|
+
}
|
|
742
|
+
return contacts;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private JSObject contactFromDataRowUri(Uri uri, String property) {
|
|
746
|
+
ContentResolver resolver = getContext().getContentResolver();
|
|
747
|
+
try (Cursor cursor = resolver.query(uri, null, null, null, null)) {
|
|
748
|
+
if (cursor == null || !cursor.moveToFirst()) {
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
String contactId = getStringColumn(cursor, ContactsContract.Data.CONTACT_ID);
|
|
753
|
+
ContactBuilder builder = new ContactBuilder(contactId != null ? contactId : uri.getLastPathSegment());
|
|
754
|
+
String displayName = getStringColumn(cursor, ContactsContract.Data.DISPLAY_NAME);
|
|
755
|
+
builder.displayName = displayName;
|
|
756
|
+
builder.fullName = displayName;
|
|
757
|
+
processDataRow(builder, cursor);
|
|
758
|
+
|
|
759
|
+
Set<String> fields = new HashSet<>();
|
|
760
|
+
fields.add("id");
|
|
761
|
+
fields.add("displayName");
|
|
762
|
+
if ("emailAddress".equals(property)) {
|
|
763
|
+
fields.add("emailAddresses");
|
|
764
|
+
} else if ("postalAddress".equals(property)) {
|
|
765
|
+
fields.add("postalAddresses");
|
|
766
|
+
} else {
|
|
767
|
+
fields.add("phoneNumbers");
|
|
768
|
+
}
|
|
769
|
+
return builder.toJSObject(fields);
|
|
770
|
+
} catch (Exception ex) {
|
|
771
|
+
android.util.Log.w("CapacitorContacts", "Failed to read picked contact data row", ex);
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
private String resolveDisplayNameFromUri(Uri contactUri) {
|
|
777
|
+
ContentResolver resolver = getContext().getContentResolver();
|
|
778
|
+
try (
|
|
779
|
+
Cursor cursor = resolver.query(contactUri, new String[] { ContactsContract.Contacts.DISPLAY_NAME_PRIMARY }, null, null, null)
|
|
780
|
+
) {
|
|
781
|
+
if (cursor != null && cursor.moveToFirst()) {
|
|
782
|
+
return cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
|
|
783
|
+
}
|
|
784
|
+
} catch (Exception ex) {
|
|
785
|
+
// The picker URI grant may only expose a subset of columns.
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private static String getStringColumn(Cursor cursor, String column) {
|
|
791
|
+
int index = cursor.getColumnIndex(column);
|
|
792
|
+
if (index < 0) {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
return cursor.getString(index);
|
|
796
|
+
}
|
|
797
|
+
|
|
593
798
|
private String getContactIdFromUri(Uri contactUri) {
|
|
594
799
|
ContentResolver resolver = getContext().getContentResolver();
|
|
595
800
|
try (Cursor cursor = resolver.query(contactUri, new String[] { ContactsContract.Contacts._ID }, null, null, null)) {
|
|
@@ -1110,6 +1315,7 @@ public class CapacitorContactsPlugin extends Plugin {
|
|
|
1110
1315
|
String id = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID));
|
|
1111
1316
|
String displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
|
|
1112
1317
|
ContactBuilder builder = new ContactBuilder(id);
|
|
1318
|
+
builder.displayName = displayName;
|
|
1113
1319
|
builder.fullName = displayName;
|
|
1114
1320
|
builderMap.put(id, builder);
|
|
1115
1321
|
}
|
|
@@ -1292,6 +1498,9 @@ public class CapacitorContactsPlugin extends Plugin {
|
|
|
1292
1498
|
if (builder.fullName == null) {
|
|
1293
1499
|
builder.fullName = resolveDisplayName(contactId);
|
|
1294
1500
|
}
|
|
1501
|
+
if (builder.displayName == null) {
|
|
1502
|
+
builder.displayName = builder.fullName;
|
|
1503
|
+
}
|
|
1295
1504
|
|
|
1296
1505
|
return builder;
|
|
1297
1506
|
}
|
|
@@ -1480,6 +1689,7 @@ public class CapacitorContactsPlugin extends Plugin {
|
|
|
1480
1689
|
String jobTitle;
|
|
1481
1690
|
String note;
|
|
1482
1691
|
String fullName;
|
|
1692
|
+
String displayName;
|
|
1483
1693
|
String photoBase64;
|
|
1484
1694
|
String accountName;
|
|
1485
1695
|
String accountType;
|
|
@@ -1612,6 +1822,7 @@ public class CapacitorContactsPlugin extends Plugin {
|
|
|
1612
1822
|
if (includeAll || fields.contains("jobTitle")) contact.put("jobTitle", jobTitle);
|
|
1613
1823
|
if (includeAll || fields.contains("note")) contact.put("note", note);
|
|
1614
1824
|
if (includeAll || fields.contains("fullName")) contact.put("fullName", fullName);
|
|
1825
|
+
if (includeAll || fields.contains("displayName")) contact.put("displayName", displayName != null ? displayName : fullName);
|
|
1615
1826
|
if (includeAll || fields.contains("photo")) contact.put("photo", photoBase64);
|
|
1616
1827
|
if (includeAll || fields.contains("groupIds")) contact.put("groupIds", groupIds);
|
|
1617
1828
|
if (includeAll || fields.contains("emailAddresses")) contact.put("emailAddresses", emailAddresses);
|
package/dist/docs.json
CHANGED
|
@@ -475,7 +475,7 @@
|
|
|
475
475
|
"text": "1.0.0"
|
|
476
476
|
}
|
|
477
477
|
],
|
|
478
|
-
"docs": "Pick a single contact using the native contact picker.",
|
|
478
|
+
"docs": "Pick a single contact using the native contact picker.\n\nSame options as {@link pickContacts}. Without `property`, Android still\nreturns the full contact when `READ_CONTACTS` is granted.",
|
|
479
479
|
"complexTypes": [
|
|
480
480
|
"PickContactsResult",
|
|
481
481
|
"PickContactOptions",
|
|
@@ -508,7 +508,7 @@
|
|
|
508
508
|
"text": "1.0.0"
|
|
509
509
|
}
|
|
510
510
|
],
|
|
511
|
-
"docs": "Pick one or more contacts using the native contact picker.",
|
|
511
|
+
"docs": "Pick one or more contacts using the native contact picker.\n\nOn iOS this never requires a contacts permission.\n\nOn Android, `pickContacts()` without `property` still returns the full\ncontact when the app has `READ_CONTACTS`. Pass `property` only when you\nwant a single phone number, email address, or postal address and can omit\nthat permission (required to target Android 17 / API 37 without a Play\nConsole contacts declaration).",
|
|
512
512
|
"complexTypes": [
|
|
513
513
|
"PickContactsResult",
|
|
514
514
|
"PickContactsOptions"
|
|
@@ -727,6 +727,18 @@
|
|
|
727
727
|
"complexTypes": [],
|
|
728
728
|
"type": "string | undefined"
|
|
729
729
|
},
|
|
730
|
+
{
|
|
731
|
+
"name": "displayName",
|
|
732
|
+
"tags": [
|
|
733
|
+
{
|
|
734
|
+
"text": "8.1.0",
|
|
735
|
+
"name": "since"
|
|
736
|
+
}
|
|
737
|
+
],
|
|
738
|
+
"docs": "Formatted name shown by the device for this contact.\n\nDerived from `CNContactFormatter` on iOS and `DISPLAY_NAME` on Android.\nRead-only: setting this when creating or updating a contact has no effect.\nUse `givenName` and `familyName` to write a name.",
|
|
739
|
+
"complexTypes": [],
|
|
740
|
+
"type": "string | undefined"
|
|
741
|
+
},
|
|
730
742
|
{
|
|
731
743
|
"name": "account",
|
|
732
744
|
"tags": [
|
|
@@ -1971,7 +1983,7 @@
|
|
|
1971
1983
|
"name": "since"
|
|
1972
1984
|
}
|
|
1973
1985
|
],
|
|
1974
|
-
"docs": "Optional list of specific fields to retrieve. If not specified, all fields are returned.",
|
|
1986
|
+
"docs": "Optional list of specific fields to retrieve. If not specified, all fields are returned.\n\nIgnored when {@link PickContactsOptions.property} is set. The result then\ncontains only `id`, `displayName`, and the selected property.",
|
|
1975
1987
|
"complexTypes": [
|
|
1976
1988
|
"ContactField"
|
|
1977
1989
|
],
|
|
@@ -1985,9 +1997,23 @@
|
|
|
1985
1997
|
"name": "since"
|
|
1986
1998
|
}
|
|
1987
1999
|
],
|
|
1988
|
-
"docs": "Whether to allow selecting multiple contacts. Default is false.",
|
|
2000
|
+
"docs": "Whether to allow selecting multiple contacts. Default is false.\n\nIgnored when {@link PickContactsOptions.property} is set. Property picking\nalways returns a single value.",
|
|
1989
2001
|
"complexTypes": [],
|
|
1990
2002
|
"type": "boolean | undefined"
|
|
2003
|
+
},
|
|
2004
|
+
{
|
|
2005
|
+
"name": "property",
|
|
2006
|
+
"tags": [
|
|
2007
|
+
{
|
|
2008
|
+
"text": "8.1.0",
|
|
2009
|
+
"name": "since"
|
|
2010
|
+
}
|
|
2011
|
+
],
|
|
2012
|
+
"docs": "Restrict the picker to a single contact property.\n\nOn Android this launches the picker against the phone, email, or postal\naddress table and reads the granted data row. That requires no\n`READ_CONTACTS` permission on any Android version, which is required for\napps that only pick a contact detail and target Android 17 (API 37) or\nlater under the Google Play Contacts Permissions policy.\n\nOn iOS the picker lets the user choose one phone number, email address,\nor postal address. It never requires a contacts permission.",
|
|
2013
|
+
"complexTypes": [
|
|
2014
|
+
"ContactProperty"
|
|
2015
|
+
],
|
|
2016
|
+
"type": "ContactProperty"
|
|
1991
2017
|
}
|
|
1992
2018
|
]
|
|
1993
2019
|
},
|
|
@@ -2103,7 +2129,47 @@
|
|
|
2103
2129
|
]
|
|
2104
2130
|
}
|
|
2105
2131
|
],
|
|
2106
|
-
"enums": [
|
|
2132
|
+
"enums": [
|
|
2133
|
+
{
|
|
2134
|
+
"name": "ContactProperty",
|
|
2135
|
+
"slug": "contactproperty",
|
|
2136
|
+
"members": [
|
|
2137
|
+
{
|
|
2138
|
+
"name": "PhoneNumber",
|
|
2139
|
+
"value": "'phoneNumber'",
|
|
2140
|
+
"tags": [
|
|
2141
|
+
{
|
|
2142
|
+
"text": "8.1.0",
|
|
2143
|
+
"name": "since"
|
|
2144
|
+
}
|
|
2145
|
+
],
|
|
2146
|
+
"docs": "A single phone number."
|
|
2147
|
+
},
|
|
2148
|
+
{
|
|
2149
|
+
"name": "EmailAddress",
|
|
2150
|
+
"value": "'emailAddress'",
|
|
2151
|
+
"tags": [
|
|
2152
|
+
{
|
|
2153
|
+
"text": "8.1.0",
|
|
2154
|
+
"name": "since"
|
|
2155
|
+
}
|
|
2156
|
+
],
|
|
2157
|
+
"docs": "A single email address."
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
"name": "PostalAddress",
|
|
2161
|
+
"value": "'postalAddress'",
|
|
2162
|
+
"tags": [
|
|
2163
|
+
{
|
|
2164
|
+
"text": "8.1.0",
|
|
2165
|
+
"name": "since"
|
|
2166
|
+
}
|
|
2167
|
+
],
|
|
2168
|
+
"docs": "A single postal address."
|
|
2169
|
+
}
|
|
2170
|
+
]
|
|
2171
|
+
}
|
|
2172
|
+
],
|
|
2107
2173
|
"typeAliases": [
|
|
2108
2174
|
{
|
|
2109
2175
|
"name": "Omit",
|