@aalzehla/capacitor-contacts 0.0.1

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.
Files changed (32) hide show
  1. package/CapacitorContacts.podspec +17 -0
  2. package/Package.swift +28 -0
  3. package/README.md +67 -0
  4. package/android/build.gradle +58 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/aalzehla/capacitor/contacts/CapacitorContactsPlugin.java +177 -0
  7. package/android/src/main/java/com/aalzehla/capacitor/contacts/ContactDataExtractorVisitor.java +122 -0
  8. package/android/src/main/java/com/aalzehla/capacitor/contacts/ContactExtractorVisitor.java +31 -0
  9. package/android/src/main/java/com/aalzehla/capacitor/contacts/PluginContactFields.java +17 -0
  10. package/android/src/main/java/com/aalzehla/capacitor/contacts/contentQuery/ContentQuery.java +83 -0
  11. package/android/src/main/java/com/aalzehla/capacitor/contacts/contentQuery/ContentQueryService.java +60 -0
  12. package/android/src/main/java/com/aalzehla/capacitor/contacts/utils/Utils.java +19 -0
  13. package/android/src/main/java/com/aalzehla/capacitor/contacts/utils/Visitable.java +5 -0
  14. package/android/src/main/java/com/aalzehla/capacitor/contacts/utils/Visitor.java +5 -0
  15. package/android/src/main/res/.gitkeep +0 -0
  16. package/dist/docs.json +145 -0
  17. package/dist/esm/definitions.d.ts +20 -0
  18. package/dist/esm/definitions.js +2 -0
  19. package/dist/esm/definitions.js.map +1 -0
  20. package/dist/esm/index.d.ts +4 -0
  21. package/dist/esm/index.js +7 -0
  22. package/dist/esm/index.js.map +1 -0
  23. package/dist/esm/web.d.ts +9 -0
  24. package/dist/esm/web.js +14 -0
  25. package/dist/esm/web.js.map +1 -0
  26. package/dist/plugin.cjs.js +31 -0
  27. package/dist/plugin.cjs.js.map +1 -0
  28. package/dist/plugin.js +34 -0
  29. package/dist/plugin.js.map +1 -0
  30. package/ios/Sources/CapacitorContactsPlugin/CapacitorContactsPlugin.swift +166 -0
  31. package/ios/Tests/CapacitorContactsPluginTests/CapacitorContactsPluginTests.swift +15 -0
  32. package/package.json +80 -0
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapacitorContacts'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '13.0'
15
+ s.dependency 'Capacitor'
16
+ s.swift_version = '5.1'
17
+ end
package/Package.swift ADDED
@@ -0,0 +1,28 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CapacitorContacts",
6
+ platforms: [.iOS(.v13)],
7
+ products: [
8
+ .library(
9
+ name: "CapacitorContacts",
10
+ targets: ["CapacitorContactsPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "main")
14
+ ],
15
+ targets: [
16
+ .target(
17
+ name: "CapacitorContactsPlugin",
18
+ dependencies: [
19
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
+ .product(name: "Cordova", package: "capacitor-swift-pm")
21
+ ],
22
+ path: "ios/Sources/CapacitorContactsPlugin"),
23
+ .testTarget(
24
+ name: "CapacitorContactsPluginTests",
25
+ dependencies: ["CapacitorContactsPlugin"],
26
+ path: "ios/Tests/CapacitorContactsPluginTests")
27
+ ]
28
+ )
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # capacitor-contacts
2
+
3
+ This capacitor plugin allows you to use the native contact picker UI on Android or iOS for single contact selection. Both platforms will return the same payload structure, where the data exists.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @aalzehla/capacitor-contacts
9
+ npx cap sync
10
+ ```
11
+
12
+ ## API
13
+
14
+ <docgen-index>
15
+
16
+ * [`open()`](#open)
17
+ * [`close()`](#close)
18
+ * [Interfaces](#interfaces)
19
+
20
+ </docgen-index>
21
+
22
+ <docgen-api>
23
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
24
+
25
+ ### open()
26
+
27
+ ```typescript
28
+ open() => Promise<Contact[]>
29
+ ```
30
+
31
+ **Returns:** <code>Promise&lt;Contact[]&gt;</code>
32
+
33
+ --------------------
34
+
35
+
36
+ ### close()
37
+
38
+ ```typescript
39
+ close() => Promise<void>
40
+ ```
41
+
42
+ --------------------
43
+
44
+
45
+ ### Interfaces
46
+
47
+
48
+ #### Contact
49
+
50
+ | Prop | Type |
51
+ | ----------------------------- | ------------------- |
52
+ | **`identifier`** | <code>string</code> |
53
+ | **`androidContactLookupKey`** | <code>string</code> |
54
+ | **`contactId`** | <code>string</code> |
55
+ | **`givenName`** | <code>string</code> |
56
+ | **`familyName`** | <code>string</code> |
57
+ | **`nickname`** | <code>string</code> |
58
+ | **`fullName`** | <code>string</code> |
59
+ | **`jobTitle`** | <code>string</code> |
60
+ | **`departmentName`** | <code>string</code> |
61
+ | **`organizationName`** | <code>string</code> |
62
+ | **`note`** | <code>string</code> |
63
+ | **`phoneNumbers`** | <code>any[]</code> |
64
+ | **`emailAddresses`** | <code>any[]</code> |
65
+ | **`postalAddresses`** | <code>any[]</code> |
66
+
67
+ </docgen-api>
@@ -0,0 +1,58 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.5'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.5.1'
6
+ }
7
+
8
+ buildscript {
9
+ repositories {
10
+ google()
11
+ mavenCentral()
12
+ }
13
+ dependencies {
14
+ classpath 'com.android.tools.build:gradle:8.2.1'
15
+ }
16
+ }
17
+
18
+ apply plugin: 'com.android.library'
19
+
20
+ android {
21
+ namespace "com.aalzehla.capacitor.contacts"
22
+ compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
23
+ defaultConfig {
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
26
+ versionCode 1
27
+ versionName "1.0"
28
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ }
30
+ buildTypes {
31
+ release {
32
+ minifyEnabled false
33
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34
+ }
35
+ }
36
+ lintOptions {
37
+ abortOnError false
38
+ }
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_17
41
+ targetCompatibility JavaVersion.VERSION_17
42
+ }
43
+ }
44
+
45
+ repositories {
46
+ google()
47
+ mavenCentral()
48
+ }
49
+
50
+
51
+ dependencies {
52
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
53
+ implementation project(':capacitor-android')
54
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55
+ testImplementation "junit:junit:$junitVersion"
56
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
57
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
58
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,177 @@
1
+ package com.aalzehla.capacitor.contacts;
2
+
3
+ import android.Manifest;
4
+ import android.content.Intent;
5
+ import android.provider.ContactsContract;
6
+ import android.provider.ContactsContract.CommonDataKinds;
7
+ import android.util.Log;
8
+
9
+ import androidx.activity.result.ActivityResult;
10
+
11
+ import com.getcapacitor.JSArray;
12
+ import com.getcapacitor.JSObject;
13
+ import com.getcapacitor.PermissionState;
14
+ import com.getcapacitor.Plugin;
15
+ import com.getcapacitor.PluginCall;
16
+ import com.getcapacitor.PluginMethod;
17
+ import com.getcapacitor.annotation.CapacitorPlugin;
18
+ import com.getcapacitor.annotation.Permission;
19
+ import com.getcapacitor.annotation.PermissionCallback;
20
+ import com.getcapacitor.annotation.ActivityCallback;
21
+
22
+ import com.aalzehla.capacitor.contacts.contentQuery.ContentQuery;
23
+ import com.aalzehla.capacitor.contacts.contentQuery.ContentQueryService;
24
+ import com.aalzehla.capacitor.contacts.utils.Utils;
25
+
26
+ import java.io.IOException;
27
+ import java.util.HashMap;
28
+ import java.util.List;
29
+ import java.util.Map;
30
+
31
+ // import android.util.Log;
32
+
33
+ @CapacitorPlugin(
34
+ name = "CapacitorContacts",
35
+ permissions={
36
+ @Permission(strings = {Manifest.permission.READ_CONTACTS}, alias = "contacts"),
37
+ }
38
+ )
39
+ public class CapacitorContactsPlugin extends Plugin {
40
+
41
+ // Messages
42
+ public static final String ERROR_READ_CONTACT = "Unable to read contact data.";
43
+ public static final String ERROR_NO_PERMISSION = "User denied permission";
44
+
45
+ // Queries
46
+ public static final String CONTACT_DATA_SELECT_CLAUSE = ContactsContract.Data.LOOKUP_KEY + " = ? AND " + ContactsContract.Data.MIMETYPE + " IN('" + CommonDataKinds.Email.CONTENT_ITEM_TYPE + "', '" + CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "', '" + CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE + "')"; //
47
+
48
+ @PluginMethod()
49
+ public void open(PluginCall call) {
50
+ if (getPermissionState("contacts") != PermissionState.GRANTED) {
51
+ //saveCall(call);
52
+ call.setKeepAlive(true);
53
+ requestPermissionForAlias("contacts", call, "contactsPermsCallback");
54
+ return;
55
+ }
56
+ //saveCall(call);
57
+ call.setKeepAlive(true);
58
+ Intent contactPickerIntent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
59
+ startActivityForResult(call, contactPickerIntent, "activityCallback");
60
+ }
61
+
62
+ @PermissionCallback
63
+ private void contactsPermsCallback(PluginCall call) {
64
+ if (getPermissionState("contacts") == PermissionState.GRANTED) {
65
+ open(call);
66
+ } else {
67
+ call.reject(ERROR_NO_PERMISSION);
68
+ }
69
+ }
70
+
71
+ @ActivityCallback
72
+ private void activityCallback(PluginCall call, ActivityResult result) {
73
+ try {
74
+ //Log.v("File: ", String.valueOf(result));
75
+ JSObject contact = readContactData(result.getData(), call);
76
+ call.resolve(contact); // since it is just the contact object and not an array, no need to return the value field but instead just return the selected contact. use contact to replace Utils.wrapIntoResult(contact)
77
+ } catch (IOException e) {
78
+ call.reject(ERROR_READ_CONTACT);
79
+ }
80
+ }
81
+
82
+ @PluginMethod()
83
+ public void close(PluginCall call) {
84
+ call.unimplemented();
85
+ }
86
+
87
+ private JSObject readContactData(Intent intent, PluginCall savedCall) throws IOException {
88
+ final Map<String, String> projectionMap = getContactProjectionMap(); ////
89
+
90
+ try {
91
+ ContentQuery contactQuery = new ContentQuery.Builder()
92
+ .withUri(intent.getData())
93
+ .withProjection(projectionMap)
94
+ .build();
95
+
96
+
97
+ try (ContentQueryService.VisitableCursorWrapper contactVcw = ContentQueryService.query(getContext(), contactQuery)) {
98
+
99
+ ContactExtractorVisitor contactExtractor = new ContactExtractorVisitor(projectionMap);
100
+ contactVcw.accept(contactExtractor);
101
+ List<JSObject> contacts = contactExtractor.getContacts();
102
+
103
+ if (contacts.size() == 0) {
104
+ return null;
105
+ } else {
106
+ JSObject chosenContact = contacts.get(0);
107
+
108
+ Map<String, String> dataProjectionMap = getContactDataProjectionMap(); ////////
109
+ ContentQuery contactDataQuery = new ContentQuery.Builder()
110
+ .withUri(ContactsContract.Data.CONTENT_URI)
111
+ .withProjection(dataProjectionMap)
112
+ .withSelection(CONTACT_DATA_SELECT_CLAUSE)
113
+ .withSelectionArgs(new String[]{chosenContact.getString(PluginContactFields.ANDROID_CONTACT_LOOKUP_KEY)})
114
+ .withSortOrder(ContactsContract.Data.MIMETYPE)
115
+ .build();
116
+
117
+ try (ContentQueryService.VisitableCursorWrapper dataVcw = ContentQueryService.query(getContext(), contactDataQuery)) {
118
+
119
+ ContactDataExtractorVisitor contactDataExtractor = new ContactDataExtractorVisitor(dataProjectionMap);
120
+ dataVcw.accept(contactDataExtractor);
121
+
122
+ return transformContactObject(chosenContact, contactDataExtractor.getEmailAddresses(), contactDataExtractor.getPhoneNumbers(), contactDataExtractor.getPostalAddresses());
123
+ }
124
+ }
125
+ }
126
+ } catch (Exception e) {
127
+ return null;
128
+ }
129
+
130
+ }
131
+
132
+ private JSObject transformContactObject(JSObject tempContact, JSArray emailAddresses, JSArray phoneNumbers, JSArray postalAddresses) {
133
+ JSObject contact = new JSObject();
134
+ contact.put(PluginContactFields.IDENTIFIER, tempContact.getString(PluginContactFields.IDENTIFIER));
135
+ contact.put(PluginContactFields.ANDROID_CONTACT_LOOKUP_KEY, tempContact.getString(PluginContactFields.ANDROID_CONTACT_LOOKUP_KEY));
136
+ String displayName = tempContact.getString(PluginContactFields.DISPLAY_NAME);
137
+ contact.put(PluginContactFields.FULL_NAME, displayName);
138
+ if (displayName != null && displayName.contains(" ")) {
139
+ contact.put(PluginContactFields.DISPLAY_NAME, displayName);
140
+ contact.put(PluginContactFields.GIVEN_NAME, displayName.split(" ")[0]);
141
+ contact.put(PluginContactFields.FAMILY_NAME, displayName.split(" ")[1]);
142
+ }
143
+ contact.put(PluginContactFields.EMAIL_ADDRESSES, emailAddresses);
144
+ contact.put(PluginContactFields.PHONE_NUMBERS, phoneNumbers);
145
+ contact.put(PluginContactFields.POSTAL_ADDRESSES, postalAddresses);
146
+ //contact.put(PluginContactFields.PHOTO_URI, tempContact.getString(PluginContactFields.PHOTO_URI));
147
+ return contact;
148
+ }
149
+
150
+ private Map<String, String> getContactProjectionMap() {
151
+ Map<String, String> contactFieldsMap = new HashMap<>();
152
+ contactFieldsMap.put(ContactsContract.Contacts._ID, PluginContactFields.IDENTIFIER);
153
+ contactFieldsMap.put(ContactsContract.Contacts.LOOKUP_KEY, PluginContactFields.ANDROID_CONTACT_LOOKUP_KEY);
154
+ contactFieldsMap.put(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, PluginContactFields.DISPLAY_NAME);
155
+
156
+ contactFieldsMap.put(ContactsContract.Contacts.PHOTO_URI, PluginContactFields.PHOTO_URI);
157
+
158
+ // contactFieldsMap.put(ContactsContract.Contacts.Data.DATA15, PluginContactFields.PHOTO_URI);
159
+ return contactFieldsMap;
160
+ }
161
+
162
+ private Map<String, String> getContactDataProjectionMap() {
163
+ Map<String, String> contactFieldsMap = new HashMap<>();
164
+ contactFieldsMap.put(CommonDataKinds.Email.MIMETYPE, PluginContactFields.MIME_TYPE);
165
+ contactFieldsMap.put(ContactsContract.Data.DATA1, ContactsContract.Data.DATA1);
166
+ contactFieldsMap.put(ContactsContract.Data.DATA2, ContactsContract.Data.DATA2);
167
+ contactFieldsMap.put(ContactsContract.Data.DATA3, ContactsContract.Data.DATA3);
168
+ contactFieldsMap.put(ContactsContract.Data.DATA4, ContactsContract.Data.DATA4);
169
+ contactFieldsMap.put(ContactsContract.Data.DATA5, ContactsContract.Data.DATA5);
170
+ contactFieldsMap.put(ContactsContract.Data.DATA6, ContactsContract.Data.DATA6);
171
+ contactFieldsMap.put(ContactsContract.Data.DATA7, ContactsContract.Data.DATA7);
172
+ contactFieldsMap.put(ContactsContract.Data.DATA8, ContactsContract.Data.DATA8);
173
+ contactFieldsMap.put(ContactsContract.Data.DATA9, ContactsContract.Data.DATA9);
174
+ contactFieldsMap.put(ContactsContract.Data.DATA10, ContactsContract.Data.DATA10);
175
+ return contactFieldsMap;
176
+ }
177
+ }
@@ -0,0 +1,122 @@
1
+ package com.aalzehla.capacitor.contacts;
2
+
3
+ import android.database.Cursor;
4
+ import android.provider.ContactsContract;
5
+ import com.getcapacitor.JSArray;
6
+ import com.getcapacitor.JSObject;
7
+ import com.aalzehla.capacitor.contacts.contentQuery.ContentQueryService;
8
+ import com.aalzehla.capacitor.contacts.utils.Visitor;
9
+
10
+ import java.util.Map;
11
+ import android.util.Log;
12
+
13
+ public class ContactDataExtractorVisitor implements Visitor<Cursor> {
14
+
15
+ private Map<String, String> projectionMap;
16
+
17
+ private JSArray phoneNumbers = new JSArray();
18
+ private JSArray emailAddresses = new JSArray();
19
+ private JSArray postalAddresses = new JSArray();
20
+
21
+ public ContactDataExtractorVisitor(Map<String, String> projectionMap) {
22
+ this.projectionMap = projectionMap;
23
+ }
24
+
25
+ @Override
26
+ public void visit(Cursor cursor) {
27
+ JSObject currentDataRecord = ContentQueryService.extractDataFromResultSet(cursor, projectionMap);
28
+ String currentMimeType = currentDataRecord.getString(PluginContactFields.MIME_TYPE);
29
+
30
+ //Log.v("HELLO", String.valueOf(currentDataRecord));
31
+
32
+ if (ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(currentMimeType)) {
33
+ JSObject email = new JSObject();
34
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA1) != null) {
35
+ email.put("emailAddress", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA1));
36
+ }
37
+ switch (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA2)) {
38
+ case "1":
39
+ email.put("type", "home");
40
+ break;
41
+ case "2":
42
+ email.put("type", "mobile");
43
+ break;
44
+ case "3":
45
+ email.put("type", "work");
46
+ break;
47
+ default:
48
+ email.put("type", "other");
49
+ break;
50
+ }
51
+ emailAddresses.put(email);
52
+ } else if (ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(currentMimeType)) {
53
+ JSObject phone = new JSObject();
54
+ // https://developer.android.com/reference/android/provider/ContactsContract.CommonDataKinds.Phone
55
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA1) != null) {
56
+ phone.put("phoneNumber", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA1));
57
+ }
58
+ switch (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA2)) {
59
+ case "1":
60
+ phone.put("type", "home");
61
+ break;
62
+ case "2":
63
+ phone.put("type", "mobile");
64
+ break;
65
+ case "3":
66
+ phone.put("type", "work");
67
+ break;
68
+ default:
69
+ phone.put("type", "other");
70
+ break;
71
+ }
72
+ phoneNumbers.put(phone);
73
+ } else if (ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE.equals(currentMimeType)) {
74
+ JSObject address = new JSObject();
75
+ // https://developer.android.com/reference/android/provider/ContactsContract.CommonDataKinds.StructuredPostal
76
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA1) != null) {
77
+ address.put("formattedAddress", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA1));
78
+ }
79
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA4) != null) {
80
+ address.put("street", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA4));
81
+ }
82
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA5) != null) {
83
+ address.put("pobox", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA5));
84
+ }
85
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA6) != null) {
86
+ address.put("neighborhood", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA6));
87
+ }
88
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA7) != null) {
89
+ address.put("city", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA7));
90
+ }
91
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA8) != null) {
92
+ address.put("state", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA8));
93
+ }
94
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA9) != null) {
95
+ address.put("postalCode", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA9));
96
+ }
97
+ if (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA10) != null) {
98
+ address.put("country", currentDataRecord.getString(ContactsContract.Contacts.Data.DATA10));
99
+ }
100
+ switch (currentDataRecord.getString(ContactsContract.Contacts.Data.DATA2)) {
101
+ case "1":
102
+ address.put("type", "home");
103
+ break;
104
+ case "2":
105
+ address.put("type", "work");
106
+ break;
107
+ default:
108
+ address.put("type", "other");
109
+ break;
110
+ }
111
+ postalAddresses.put(address);
112
+ }
113
+ }
114
+
115
+ public JSArray getPhoneNumbers() {
116
+ return phoneNumbers;
117
+ }
118
+ public JSArray getEmailAddresses() {
119
+ return emailAddresses;
120
+ }
121
+ public JSArray getPostalAddresses() { return postalAddresses; }
122
+ }
@@ -0,0 +1,31 @@
1
+ package com.aalzehla.capacitor.contacts;
2
+
3
+ import android.database.Cursor;
4
+ import com.getcapacitor.JSObject;
5
+ import com.aalzehla.capacitor.contacts.contentQuery.ContentQueryService;
6
+ import com.aalzehla.capacitor.contacts.utils.Visitor;
7
+
8
+ import java.util.ArrayList;
9
+ import java.util.List;
10
+ import java.util.Map;
11
+
12
+ public class ContactExtractorVisitor implements Visitor<Cursor> {
13
+
14
+ private Map<String, String> projectionMap;
15
+
16
+ private List<JSObject> contacts = new ArrayList<>();
17
+
18
+ public ContactExtractorVisitor(Map<String, String> projectionMap) {
19
+ this.projectionMap = projectionMap;
20
+ }
21
+
22
+ @Override
23
+ public void visit(Cursor cursor) {
24
+ JSObject contact = ContentQueryService.extractDataFromResultSet(cursor, projectionMap);
25
+ contacts.add(contact);
26
+ }
27
+
28
+ public List<JSObject> getContacts() {
29
+ return contacts;
30
+ }
31
+ }
@@ -0,0 +1,17 @@
1
+ package com.aalzehla.capacitor.contacts;
2
+
3
+ public class PluginContactFields {
4
+ public static final String IDENTIFIER = "identifier";
5
+ public static final String ANDROID_CONTACT_LOOKUP_KEY = "androidContactLookupKey";
6
+ public static final String DISPLAY_NAME = "displayName";
7
+ public static final String FULL_NAME = "fullName";
8
+ public static final String GIVEN_NAME = "givenName";
9
+ public static final String FAMILY_NAME = "familyName";
10
+ public static final String EMAIL_ADDRESSES = "emailAddresses";
11
+ public static final String PHONE_NUMBERS = "phoneNumbers";
12
+ public static final String PHONE_TYPES = "phoneNumberLabels";
13
+ public static final String POSTAL_ADDRESSES = "postalAddresses";
14
+ public static final String POSTAL_TYPES = "postalAddressLabels";
15
+ public static final String MIME_TYPE = "mimeType";
16
+ public static final String PHOTO_URI = "image";
17
+ }
@@ -0,0 +1,83 @@
1
+ package com.aalzehla.capacitor.contacts.contentQuery;
2
+
3
+ import android.net.Uri;
4
+ import android.os.CancellationSignal;
5
+
6
+ import java.util.Map;
7
+
8
+ public class ContentQuery {
9
+
10
+ private Uri uri;
11
+ private Map<String, String> projection;
12
+ private String selection;
13
+ private String[] selectionArgs;
14
+ private String sortOrder;
15
+ private CancellationSignal cancellationSignal;
16
+
17
+ private ContentQuery() {
18
+ }
19
+
20
+ public Uri getUri() {
21
+ return uri;
22
+ }
23
+
24
+ public Map<String, String> getProjection() {
25
+ return projection;
26
+ }
27
+
28
+ public String getSelection() {
29
+ return selection;
30
+ }
31
+
32
+ public String[] getSelectionArgs() {
33
+ return selectionArgs;
34
+ }
35
+
36
+ public String getSortOrder() {
37
+ return sortOrder;
38
+ }
39
+
40
+ public CancellationSignal getCancellationSignal() {
41
+ return cancellationSignal;
42
+ }
43
+
44
+ public static class Builder {
45
+
46
+ private ContentQuery contentQuery = new ContentQuery();
47
+
48
+ public Builder withUri(Uri uri) {
49
+ contentQuery.uri = uri;
50
+ return this;
51
+ }
52
+
53
+ public Builder withProjection(Map<String, String> projection) {
54
+ contentQuery.projection = projection;
55
+ return this;
56
+ }
57
+
58
+ public Builder withSelection(String selection) {
59
+ contentQuery.selection = selection;
60
+ return this;
61
+ }
62
+
63
+ public Builder withSelectionArgs(String[] selectionArgs) {
64
+ contentQuery.selectionArgs = selectionArgs;
65
+ return this;
66
+ }
67
+
68
+ public Builder withSortOrder(String sortOrder) {
69
+ contentQuery.sortOrder = sortOrder;
70
+ return this;
71
+ }
72
+
73
+ public Builder withCancellationSignal(CancellationSignal cancellationSignal) {
74
+ contentQuery.cancellationSignal = cancellationSignal;
75
+ return this;
76
+ }
77
+
78
+ public ContentQuery build() {
79
+ return contentQuery;
80
+ }
81
+
82
+ }
83
+ }
@@ -0,0 +1,60 @@
1
+ package com.aalzehla.capacitor.contacts.contentQuery;
2
+
3
+ import android.content.Context;
4
+ import android.database.Cursor;
5
+ import com.getcapacitor.JSObject;
6
+ import com.aalzehla.capacitor.contacts.utils.Utils;
7
+ import com.aalzehla.capacitor.contacts.utils.Visitable;
8
+ import com.aalzehla.capacitor.contacts.utils.Visitor;
9
+
10
+ import java.io.Closeable;
11
+ import java.util.Map;
12
+
13
+ public class ContentQueryService {
14
+
15
+ public static VisitableCursorWrapper query(Context context, ContentQuery query) {
16
+ try {
17
+ String[] projectionArray = Utils.getMapKeysAsArray(query.getProjection());
18
+ Cursor cursor = context.getContentResolver().query(query.getUri(), projectionArray, query.getSelection(), query.getSelectionArgs(), query.getSortOrder(), query.getCancellationSignal());
19
+ return new VisitableCursorWrapper(cursor);
20
+ } catch (Exception e) {
21
+ throw new RuntimeException(e);
22
+ }
23
+ }
24
+
25
+ public static JSObject extractDataFromResultSet(Cursor cursor, Map<String, String> projectionMap) {
26
+ try {
27
+ JSObject result = new JSObject();
28
+ for (Map.Entry<String, String> entry : projectionMap.entrySet()) {
29
+ int columnIndex = cursor.getColumnIndex(entry.getKey());
30
+ result.put(entry.getValue(), cursor.getString(columnIndex));
31
+ }
32
+ return result;
33
+ } catch (Exception e) {
34
+ throw new RuntimeException(e);
35
+ }
36
+ }
37
+
38
+ public static class VisitableCursorWrapper implements Visitable<Cursor>, Closeable, AutoCloseable {
39
+
40
+ private Cursor cursor;
41
+
42
+ private VisitableCursorWrapper(Cursor cursor) {
43
+ this.cursor = cursor;
44
+ }
45
+
46
+ public void accept(Visitor<Cursor> visitor) {
47
+ while (cursor != null && cursor.moveToNext()) {
48
+ visitor.visit(cursor);
49
+ }
50
+ }
51
+
52
+ @Override
53
+ public void close() {
54
+ if (cursor != null) {
55
+ cursor.close();
56
+ }
57
+ }
58
+ }
59
+
60
+ }
@@ -0,0 +1,19 @@
1
+ package com.aalzehla.capacitor.contacts.utils;
2
+
3
+ import com.getcapacitor.JSObject;
4
+
5
+ import java.util.Map;
6
+
7
+ public class Utils {
8
+
9
+ public static String[] getMapKeysAsArray(Map<String, ?> map) {
10
+ return map.keySet().toArray(new String[]{});
11
+ }
12
+
13
+ public static JSObject wrapIntoResult(JSObject contact) {
14
+ JSObject result = new JSObject();
15
+ result.put("value", contact);
16
+ return result;
17
+ }
18
+
19
+ }
@@ -0,0 +1,5 @@
1
+ package com.aalzehla.capacitor.contacts.utils;
2
+
3
+ public interface Visitable<T> {
4
+ void accept(Visitor<T> visitor);
5
+ }
@@ -0,0 +1,5 @@
1
+ package com.aalzehla.capacitor.contacts.utils;
2
+
3
+ public interface Visitor<T> {
4
+ void visit(T element);
5
+ }
File without changes
package/dist/docs.json ADDED
@@ -0,0 +1,145 @@
1
+ {
2
+ "api": {
3
+ "name": "CapacitorContactsPlugin",
4
+ "slug": "capacitorcontactsplugin",
5
+ "docs": "",
6
+ "tags": [],
7
+ "methods": [
8
+ {
9
+ "name": "open",
10
+ "signature": "() => Promise<Contact[]>",
11
+ "parameters": [],
12
+ "returns": "Promise<Contact[]>",
13
+ "tags": [],
14
+ "docs": "",
15
+ "complexTypes": [
16
+ "Contact"
17
+ ],
18
+ "slug": "open"
19
+ },
20
+ {
21
+ "name": "close",
22
+ "signature": "() => Promise<void>",
23
+ "parameters": [],
24
+ "returns": "Promise<void>",
25
+ "tags": [],
26
+ "docs": "",
27
+ "complexTypes": [],
28
+ "slug": "close"
29
+ }
30
+ ],
31
+ "properties": []
32
+ },
33
+ "interfaces": [
34
+ {
35
+ "name": "Contact",
36
+ "slug": "contact",
37
+ "docs": "",
38
+ "tags": [],
39
+ "methods": [],
40
+ "properties": [
41
+ {
42
+ "name": "identifier",
43
+ "tags": [],
44
+ "docs": "",
45
+ "complexTypes": [],
46
+ "type": "string | undefined"
47
+ },
48
+ {
49
+ "name": "androidContactLookupKey",
50
+ "tags": [],
51
+ "docs": "",
52
+ "complexTypes": [],
53
+ "type": "string | undefined"
54
+ },
55
+ {
56
+ "name": "contactId",
57
+ "tags": [],
58
+ "docs": "",
59
+ "complexTypes": [],
60
+ "type": "string | undefined"
61
+ },
62
+ {
63
+ "name": "givenName",
64
+ "tags": [],
65
+ "docs": "",
66
+ "complexTypes": [],
67
+ "type": "string | undefined"
68
+ },
69
+ {
70
+ "name": "familyName",
71
+ "tags": [],
72
+ "docs": "",
73
+ "complexTypes": [],
74
+ "type": "string | undefined"
75
+ },
76
+ {
77
+ "name": "nickname",
78
+ "tags": [],
79
+ "docs": "",
80
+ "complexTypes": [],
81
+ "type": "string | undefined"
82
+ },
83
+ {
84
+ "name": "fullName",
85
+ "tags": [],
86
+ "docs": "",
87
+ "complexTypes": [],
88
+ "type": "string | undefined"
89
+ },
90
+ {
91
+ "name": "jobTitle",
92
+ "tags": [],
93
+ "docs": "",
94
+ "complexTypes": [],
95
+ "type": "string | undefined"
96
+ },
97
+ {
98
+ "name": "departmentName",
99
+ "tags": [],
100
+ "docs": "",
101
+ "complexTypes": [],
102
+ "type": "string | undefined"
103
+ },
104
+ {
105
+ "name": "organizationName",
106
+ "tags": [],
107
+ "docs": "",
108
+ "complexTypes": [],
109
+ "type": "string | undefined"
110
+ },
111
+ {
112
+ "name": "note",
113
+ "tags": [],
114
+ "docs": "",
115
+ "complexTypes": [],
116
+ "type": "string | undefined"
117
+ },
118
+ {
119
+ "name": "phoneNumbers",
120
+ "tags": [],
121
+ "docs": "",
122
+ "complexTypes": [],
123
+ "type": "any[] | undefined"
124
+ },
125
+ {
126
+ "name": "emailAddresses",
127
+ "tags": [],
128
+ "docs": "",
129
+ "complexTypes": [],
130
+ "type": "any[] | undefined"
131
+ },
132
+ {
133
+ "name": "postalAddresses",
134
+ "tags": [],
135
+ "docs": "",
136
+ "complexTypes": [],
137
+ "type": "any[] | undefined"
138
+ }
139
+ ]
140
+ }
141
+ ],
142
+ "enums": [],
143
+ "typeAliases": [],
144
+ "pluginConfigs": []
145
+ }
@@ -0,0 +1,20 @@
1
+ export interface CapacitorContactsPlugin {
2
+ open?(): Promise<Contact[]>;
3
+ close?(): Promise<void>;
4
+ }
5
+ export interface Contact {
6
+ identifier?: string;
7
+ androidContactLookupKey?: string;
8
+ contactId?: string;
9
+ givenName?: string;
10
+ familyName?: string;
11
+ nickname?: string;
12
+ fullName?: string;
13
+ jobTitle?: string;
14
+ departmentName?: string;
15
+ organizationName?: string;
16
+ note?: string;
17
+ phoneNumbers?: any[];
18
+ emailAddresses?: any[];
19
+ postalAddresses?: any[];
20
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface CapacitorContactsPlugin {\n open?(): Promise<Contact[]>;\n close?(): Promise<void>;\n}\n\nexport interface Contact {\n identifier?: string;\n androidContactLookupKey?: string;\n contactId?: string;\n givenName?: string;\n familyName?: string;\n nickname?: string;\n fullName?: string;\n jobTitle?: string;\n departmentName?: string;\n organizationName?: string;\n note?: string;\n phoneNumbers?: any[];\n emailAddresses?: any[]\n postalAddresses?: any[]\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { CapacitorContactsPlugin } from './definitions';
2
+ declare const CapacitorContacts: CapacitorContactsPlugin;
3
+ export * from './definitions';
4
+ export { CapacitorContacts };
@@ -0,0 +1,7 @@
1
+ import { registerPlugin } from '@capacitor/core';
2
+ const CapacitorContacts = registerPlugin('CapacitorContacts', {
3
+ web: () => import('./web').then(m => new m.CapacitorContactsWeb()),
4
+ });
5
+ export * from './definitions';
6
+ export { CapacitorContacts };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,iBAAiB,GAAG,cAAc,CACtC,mBAAmB,EACnB;IACE,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;CACnE,CACF,CAAC;AAEF,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { CapacitorContactsPlugin } from './definitions';\n\nconst CapacitorContacts = registerPlugin<CapacitorContactsPlugin>(\n 'CapacitorContacts',\n {\n web: () => import('./web').then(m => new m.CapacitorContactsWeb()),\n },\n);\n\nexport * from './definitions';\nexport { CapacitorContacts };\n"]}
@@ -0,0 +1,9 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ import type { CapacitorContactsPlugin } from './definitions';
3
+ export declare class CapacitorContactsWeb extends WebPlugin implements CapacitorContactsPlugin {
4
+ constructor();
5
+ close(): Promise<void>;
6
+ }
7
+ declare const ContactPicker: CapacitorContactsWeb;
8
+ declare const CapacitorContacts: CapacitorContactsWeb;
9
+ export { ContactPicker, CapacitorContacts };
@@ -0,0 +1,14 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ export class CapacitorContactsWeb extends WebPlugin {
3
+ constructor() {
4
+ super({
5
+ name: 'CapacitorContacts',
6
+ platforms: ['web']
7
+ });
8
+ }
9
+ async close() { }
10
+ }
11
+ const ContactPicker = new CapacitorContactsWeb();
12
+ const CapacitorContacts = ContactPicker;
13
+ export { ContactPicker, CapacitorContacts };
14
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,OAAO,oBACX,SAAQ,SAAS;IAGjB;QACE,KAAK,CAAC;YACF,IAAI,EAAE,mBAAmB;YACzB,SAAS,EAAE,CAAC,KAAK,CAAC;SACrB,CAAC,CAAC;IACL,CAAC;IACD,KAAK,CAAC,KAAK,KAAI,CAAC;CACjB;AAED,MAAM,aAAa,GAAG,IAAI,oBAAoB,EAAE,CAAC;AACjD,MAAM,iBAAiB,GAAG,aAAa,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,CAAC","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type { CapacitorContactsPlugin } from './definitions';\n\nexport class CapacitorContactsWeb\n extends WebPlugin\n implements CapacitorContactsPlugin\n{\n constructor() {\n super({\n name: 'CapacitorContacts',\n platforms: ['web']\n });\n }\n async close() {}\n}\n\nconst ContactPicker = new CapacitorContactsWeb();\nconst CapacitorContacts = ContactPicker\nexport { ContactPicker, CapacitorContacts };\n"]}
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var core = require('@capacitor/core');
6
+
7
+ const CapacitorContacts$1 = core.registerPlugin('CapacitorContacts', {
8
+ web: () => Promise.resolve().then(function () { return web; }).then(m => new m.CapacitorContactsWeb()),
9
+ });
10
+
11
+ class CapacitorContactsWeb extends core.WebPlugin {
12
+ constructor() {
13
+ super({
14
+ name: 'CapacitorContacts',
15
+ platforms: ['web']
16
+ });
17
+ }
18
+ async close() { }
19
+ }
20
+ const ContactPicker = new CapacitorContactsWeb();
21
+ const CapacitorContacts = ContactPicker;
22
+
23
+ var web = /*#__PURE__*/Object.freeze({
24
+ __proto__: null,
25
+ CapacitorContactsWeb: CapacitorContactsWeb,
26
+ ContactPicker: ContactPicker,
27
+ CapacitorContacts: CapacitorContacts
28
+ });
29
+
30
+ exports.CapacitorContacts = CapacitorContacts$1;
31
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst CapacitorContacts = registerPlugin('CapacitorContacts', {\n web: () => import('./web').then(m => new m.CapacitorContactsWeb()),\n});\nexport * from './definitions';\nexport { CapacitorContacts };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class CapacitorContactsWeb extends WebPlugin {\n constructor() {\n super({\n name: 'CapacitorContacts',\n platforms: ['web']\n });\n }\n async close() { }\n}\nconst ContactPicker = new CapacitorContactsWeb();\nconst CapacitorContacts = ContactPicker;\nexport { ContactPicker, CapacitorContacts };\n//# sourceMappingURL=web.js.map"],"names":["CapacitorContacts","registerPlugin","WebPlugin"],"mappings":";;;;;;AACK,MAACA,mBAAiB,GAAGC,mBAAc,CAAC,mBAAmB,EAAE;AAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;AACtE,CAAC;;ACFM,MAAM,oBAAoB,SAASC,cAAS,CAAC;AACpD,IAAI,WAAW,GAAG;AAClB,QAAQ,KAAK,CAAC;AACd,YAAY,IAAI,EAAE,mBAAmB;AACrC,YAAY,SAAS,EAAE,CAAC,KAAK,CAAC;AAC9B,SAAS,CAAC,CAAC;AACX,KAAK;AACL,IAAI,MAAM,KAAK,GAAG,GAAG;AACrB,CAAC;AACD,MAAM,aAAa,GAAG,IAAI,oBAAoB,EAAE,CAAC;AACjD,MAAM,iBAAiB,GAAG,aAAa;;;;;;;;;;;"}
package/dist/plugin.js ADDED
@@ -0,0 +1,34 @@
1
+ var capacitorCapacitorContacts = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ const CapacitorContacts$1 = core.registerPlugin('CapacitorContacts', {
5
+ web: () => Promise.resolve().then(function () { return web; }).then(m => new m.CapacitorContactsWeb()),
6
+ });
7
+
8
+ class CapacitorContactsWeb extends core.WebPlugin {
9
+ constructor() {
10
+ super({
11
+ name: 'CapacitorContacts',
12
+ platforms: ['web']
13
+ });
14
+ }
15
+ async close() { }
16
+ }
17
+ const ContactPicker = new CapacitorContactsWeb();
18
+ const CapacitorContacts = ContactPicker;
19
+
20
+ var web = /*#__PURE__*/Object.freeze({
21
+ __proto__: null,
22
+ CapacitorContactsWeb: CapacitorContactsWeb,
23
+ ContactPicker: ContactPicker,
24
+ CapacitorContacts: CapacitorContacts
25
+ });
26
+
27
+ exports.CapacitorContacts = CapacitorContacts$1;
28
+
29
+ Object.defineProperty(exports, '__esModule', { value: true });
30
+
31
+ return exports;
32
+
33
+ })({}, capacitorExports);
34
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst CapacitorContacts = registerPlugin('CapacitorContacts', {\n web: () => import('./web').then(m => new m.CapacitorContactsWeb()),\n});\nexport * from './definitions';\nexport { CapacitorContacts };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class CapacitorContactsWeb extends WebPlugin {\n constructor() {\n super({\n name: 'CapacitorContacts',\n platforms: ['web']\n });\n }\n async close() { }\n}\nconst ContactPicker = new CapacitorContactsWeb();\nconst CapacitorContacts = ContactPicker;\nexport { ContactPicker, CapacitorContacts };\n//# sourceMappingURL=web.js.map"],"names":["CapacitorContacts","registerPlugin","WebPlugin"],"mappings":";;;AACK,UAACA,mBAAiB,GAAGC,mBAAc,CAAC,mBAAmB,EAAE;IAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;IACtE,CAAC;;ICFM,MAAM,oBAAoB,SAASC,cAAS,CAAC;IACpD,IAAI,WAAW,GAAG;IAClB,QAAQ,KAAK,CAAC;IACd,YAAY,IAAI,EAAE,mBAAmB;IACrC,YAAY,SAAS,EAAE,CAAC,KAAK,CAAC;IAC9B,SAAS,CAAC,CAAC;IACX,KAAK;IACL,IAAI,MAAM,KAAK,GAAG,GAAG;IACrB,CAAC;IACD,MAAM,aAAa,GAAG,IAAI,oBAAoB,EAAE,CAAC;IACjD,MAAM,iBAAiB,GAAG,aAAa;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,166 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import ContactsUI
4
+
5
+ /**
6
+ * Please read the Capacitor iOS Plugin Development Guide
7
+ * here: https://capacitorjs.com/docs/plugins/ios
8
+ */
9
+ @objc(CapacitorContactsPlugin)
10
+ public class CapacitorContactsPlugin: CAPPlugin, CAPBridgedPlugin, CNContactPickerDelegate {
11
+ public let identifier = "CapacitorContactsPlugin"
12
+ public let jsName = "CapacitorContacts"
13
+ public let pluginMethods: [CAPPluginMethod] = [
14
+ CAPPluginMethod(name: "open", returnType: CAPPluginReturnPromise),
15
+ CAPPluginMethod(name: "close", returnType: CAPPluginReturnPromise)
16
+ ]
17
+
18
+ var vc: CNContactPickerViewController?
19
+ var id: String?
20
+
21
+ @objc func open(_ call: CAPPluginCall) {
22
+ self.id = call.callbackId
23
+ call.keepAlive = true
24
+ Permissions.contactPermission { granted in
25
+ if granted {
26
+ DispatchQueue.main.async {
27
+ self.vc = CNContactPickerViewController()
28
+ self.vc!.delegate = self
29
+ self.bridge?.viewController?.present(self.vc!, animated: true, completion: nil)
30
+ }
31
+ } else {
32
+ call.reject("User denied access to contacts")
33
+ }
34
+ }
35
+ }
36
+
37
+ @objc func close(_ call: CAPPluginCall) {
38
+ if vc == nil {
39
+ call.resolve()
40
+ }
41
+ DispatchQueue.main.async {
42
+ self.bridge?.dismissVC(animated: true) {
43
+ call.resolve()
44
+ }
45
+ }
46
+ }
47
+
48
+ func makeContact(_ contact: CNContact) -> Dictionary<String, Any> {
49
+ var res: [String:Any] = [:]
50
+ res["contactId"] = contact.identifier;
51
+ res["givenName"] = contact.givenName;
52
+ res["familyName"] = contact.familyName;
53
+ res["nickname"] = contact.nickname;
54
+ res["displayName"] = contact.givenName + " " + contact.familyName;
55
+ res["jobTitle"] = contact.jobTitle;
56
+ res["departmentName"] = contact.departmentName;
57
+ res["organizationName"] = contact.organizationName;
58
+ res["note"] = contact.note;
59
+
60
+ // process email addresses
61
+ var array: [JSObject] = [];
62
+ for emailAddress in contact.emailAddresses {
63
+ var object = JSObject()
64
+ object["type"] = CNLabeledValue<NSString>.localizedString(forLabel: emailAddress.label ?? "")
65
+ if emailAddress.value != "" {
66
+ object["emailAddress"] = emailAddress.value as String
67
+ }
68
+ array.append(object)
69
+ }
70
+ res["emailAddresses"] = array
71
+
72
+ // process phone numbers
73
+ array = [];
74
+ for phoneNumber in contact.phoneNumbers {
75
+ var object = JSObject()
76
+ object["type"] = CNLabeledValue<NSString>.localizedString(forLabel: phoneNumber.label ?? "")
77
+ if phoneNumber.value.stringValue != "" {
78
+ object["phoneNumber"] = phoneNumber.value.stringValue
79
+ }
80
+ array.append(object)
81
+ }
82
+ res["phoneNumbers"] = array
83
+
84
+ // process postal addresses
85
+ array = [];
86
+ for address in contact.postalAddresses {
87
+ var object = JSObject()
88
+ object["formattedAddress"] = CNPostalAddressFormatter().string(from: address.value)
89
+ object["type"] = CNLabeledValue<NSString>.localizedString(forLabel: address.label ?? "")
90
+ if address.value.street != "" {
91
+ object["street"] = address.value.street
92
+ }
93
+ if address.value.city != "" {
94
+ object["city"] = address.value.city
95
+ }
96
+ if address.value.state != "" {
97
+ object["state"] = address.value.state
98
+ }
99
+ if address.value.postalCode != "" {
100
+ object["postalCode"] = address.value.postalCode
101
+ }
102
+ if address.value.country != "" {
103
+ object["country"] = address.value.country
104
+ }
105
+ if address.value.isoCountryCode != "" {
106
+ object["isoCountryCode"] = address.value.isoCountryCode
107
+ }
108
+ if address.value.subAdministrativeArea != "" {
109
+ object["subAdministrativeArea"] = address.value.subAdministrativeArea
110
+ }
111
+ if address.value.subLocality != "" {
112
+ object["subLocality"] = address.value.subLocality
113
+ }
114
+ array.append(object)
115
+ }
116
+ res["postalAddresses"] = array
117
+
118
+ // temporarily disable returning contact imageData because base64 data overwhelms console log
119
+ /*if contact.imageData != nil {
120
+ let image = UIImage(data: contact.imageData!)?.pngData() ?? UIImage(data: contact.imageData!)?.jpegData(compressionQuality: 0);
121
+ res["image"] = image!.base64EncodedString(options: .lineLength64Characters);
122
+ }*/
123
+
124
+ return res
125
+ }
126
+
127
+ // didSelect contacts: [CNContact]
128
+ public func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
129
+ picker.dismiss(animated: true, completion: nil)
130
+ guard let call = self.bridge?.savedCall(withID: self.id!) else {
131
+ print("call was not loaded correctly")
132
+ return
133
+ }
134
+ //print("result: " + String(describing: contact))
135
+ call.resolve(makeContact(contact));
136
+ }
137
+
138
+ public func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
139
+ //print("closed!")
140
+ picker.dismiss(animated: true, completion: nil)
141
+ guard let call = self.bridge?.savedCall(withID: self.id!) else { return }
142
+ call.resolve()
143
+ }
144
+ }
145
+
146
+ class Permissions {
147
+ class func contactPermission(completionHandler: @escaping (_ accessGranted: Bool) -> Void) {
148
+ let contactStore = CNContactStore()
149
+ switch CNContactStore.authorizationStatus(for: .contacts) {
150
+ case .authorized:
151
+ completionHandler(true)
152
+ case .denied:
153
+ completionHandler(false)
154
+ case .restricted, .notDetermined:
155
+ contactStore.requestAccess(for: .contacts) { granted, _ in
156
+ if granted {
157
+ completionHandler(true)
158
+ } else {
159
+ DispatchQueue.main.async {
160
+ completionHandler(false)
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+ @testable import CapacitorContactsPlugin
3
+
4
+ class CapacitorContactsTests: XCTestCase {
5
+ func testEcho() {
6
+ // This is an example of a functional test case for a plugin.
7
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
8
+
9
+ let implementation = CapacitorContacts()
10
+ let value = "Hello, World!"
11
+ let result = implementation.echo(value)
12
+
13
+ XCTAssertEqual(value, result)
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@aalzehla/capacitor-contacts",
3
+ "version": "0.0.1",
4
+ "description": "This capacitor plugin allows you to use the native contact picker UI on Android or iOS for single contact selection. Both platforms will return the same payload structure, where the data exists.",
5
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "dist/",
13
+ "ios/Sources",
14
+ "ios/Tests",
15
+ "Package.swift",
16
+ "CapacitorContacts.podspec"
17
+ ],
18
+ "author": "Ateik Alzehla",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/aalzehla/capacitor-contacts.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/aalzehla/capacitor-contacts/issues"
26
+ },
27
+ "keywords": [
28
+ "capacitor",
29
+ "plugin",
30
+ "native"
31
+ ],
32
+ "scripts": {
33
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
34
+ "verify:ios": "xcodebuild -scheme CapacitorContacts -destination generic/platform=iOS",
35
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
36
+ "verify:web": "npm run build",
37
+ "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
38
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
39
+ "eslint": "eslint . --ext ts",
40
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
41
+ "swiftlint": "node-swiftlint",
42
+ "docgen": "docgen --api CapacitorContactsPlugin --output-readme README.md --output-json dist/docs.json",
43
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.js",
44
+ "clean": "rimraf ./dist",
45
+ "watch": "tsc --watch",
46
+ "prepublishOnly": "npm run build"
47
+ },
48
+ "devDependencies": {
49
+ "@capacitor/android": "^6.0.0",
50
+ "@capacitor/core": "^6.0.0",
51
+ "@capacitor/docgen": "^0.2.2",
52
+ "@capacitor/ios": "^6.0.0",
53
+ "@ionic/eslint-config": "^0.4.0",
54
+ "@ionic/prettier-config": "^1.0.1",
55
+ "@ionic/swiftlint-config": "^1.1.2",
56
+ "eslint": "^8.57.0",
57
+ "prettier": "~2.3.0",
58
+ "prettier-plugin-java": "~1.0.2",
59
+ "rimraf": "^3.0.2",
60
+ "rollup": "^2.32.0",
61
+ "swiftlint": "^1.0.1",
62
+ "typescript": "~4.1.5"
63
+ },
64
+ "peerDependencies": {
65
+ "@capacitor/core": "^6.0.0"
66
+ },
67
+ "prettier": "@ionic/prettier-config",
68
+ "swiftlint": "@ionic/swiftlint-config",
69
+ "eslintConfig": {
70
+ "extends": "@ionic/eslint-config/recommended"
71
+ },
72
+ "capacitor": {
73
+ "ios": {
74
+ "src": "ios"
75
+ },
76
+ "android": {
77
+ "src": "android"
78
+ }
79
+ }
80
+ }