@capgo/capacitor-contacts 8.0.4 → 8.0.5

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.
@@ -39,7 +39,8 @@ import java.util.Set;
39
39
  )
40
40
  public class CapacitorContactsPlugin extends Plugin {
41
41
 
42
- private final String pluginVersion = "8.0.4";
42
+ private final String pluginVersion = "8.0.5";
43
+ private static final int BATCH_SIZE = 50;
43
44
 
44
45
  // MARK: - Implemented API surface
45
46
 
@@ -76,15 +77,15 @@ public class CapacitorContactsPlugin extends Plugin {
76
77
  return;
77
78
  }
78
79
 
79
- JSObject options = call.getObject("options", new JSObject());
80
- Integer limit = options.has("limit") ? options.getInteger("limit") : null;
81
- Integer offset = options.has("offset") ? options.getInteger("offset") : null;
80
+ Integer limit = call.getInt("limit", null);
81
+ Integer offset = call.getInt("offset", null);
82
+ Set<String> fields = parseFieldsArray(call);
82
83
 
83
84
  try {
84
- List<ContactBuilder> builders = fetchContacts(limit, offset);
85
+ List<ContactBuilder> builders = fetchContacts(limit, offset, fields);
85
86
  JSArray contacts = new JSArray();
86
87
  for (ContactBuilder builder : builders) {
87
- contacts.put(builder.toJSObject());
88
+ contacts.put(builder.toJSObject(fields));
88
89
  }
89
90
  JSObject result = new JSObject();
90
91
  result.put("contacts", contacts);
@@ -101,21 +102,22 @@ public class CapacitorContactsPlugin extends Plugin {
101
102
  return;
102
103
  }
103
104
 
104
- JSObject options = call.getObject("options", new JSObject());
105
- String identifier = options.getString("id");
105
+ String identifier = call.getString("id");
106
106
  if (identifier == null) {
107
107
  call.reject("Missing contact identifier.");
108
108
  return;
109
109
  }
110
110
 
111
111
  try {
112
- ContactBuilder builder = fetchContact(identifier);
112
+ Set<String> fields = parseFieldsArray(call);
113
+ ContactBuilder builder = fetchContact(identifier, fields);
113
114
  if (builder == null) {
114
115
  call.resolve(new JSObject().put("contact", null));
115
116
  return;
116
117
  }
118
+
117
119
  JSObject result = new JSObject();
118
- result.put("contact", builder.toJSObject());
120
+ result.put("contact", builder.toJSObject(fields));
119
121
  call.resolve(result);
120
122
  } catch (Exception ex) {
121
123
  call.reject("Failed to fetch contact.", null, ex);
@@ -215,8 +217,7 @@ public class CapacitorContactsPlugin extends Plugin {
215
217
  return;
216
218
  }
217
219
 
218
- JSObject options = call.getObject("options", new JSObject());
219
- JSObject contactData = options.getJSObject("contact");
220
+ JSObject contactData = call.getObject("contact");
220
221
  if (contactData == null) {
221
222
  call.reject("Missing contact data.");
222
223
  return;
@@ -237,9 +238,8 @@ public class CapacitorContactsPlugin extends Plugin {
237
238
  return;
238
239
  }
239
240
 
240
- JSObject options = call.getObject("options", new JSObject());
241
- String contactId = options.getString("id");
242
- JSObject contactData = options.getJSObject("contact");
241
+ String contactId = call.getString("id");
242
+ JSObject contactData = call.getObject("contact");
243
243
 
244
244
  if (contactId == null || contactData == null) {
245
245
  call.reject("Missing contact identifier or data.");
@@ -261,8 +261,7 @@ public class CapacitorContactsPlugin extends Plugin {
261
261
  return;
262
262
  }
263
263
 
264
- JSObject options = call.getObject("options", new JSObject());
265
- String contactId = options.getString("id");
264
+ String contactId = call.getString("id");
266
265
 
267
266
  if (contactId == null) {
268
267
  call.reject("Missing contact identifier.");
@@ -327,8 +326,7 @@ public class CapacitorContactsPlugin extends Plugin {
327
326
  return;
328
327
  }
329
328
 
330
- JSObject options = call.getObject("options", new JSObject());
331
- String groupId = options.getString("id");
329
+ String groupId = call.getString("id");
332
330
 
333
331
  if (groupId == null) {
334
332
  call.reject("Missing group identifier.");
@@ -368,8 +366,7 @@ public class CapacitorContactsPlugin extends Plugin {
368
366
  return;
369
367
  }
370
368
 
371
- JSObject options = call.getObject("options", new JSObject());
372
- JSObject groupData = options.getJSObject("group");
369
+ JSObject groupData = call.getObject("group");
373
370
 
374
371
  if (groupData == null) {
375
372
  call.reject("Missing group data.");
@@ -411,8 +408,7 @@ public class CapacitorContactsPlugin extends Plugin {
411
408
  return;
412
409
  }
413
410
 
414
- JSObject options = call.getObject("options", new JSObject());
415
- String groupId = options.getString("id");
411
+ String groupId = call.getString("id");
416
412
 
417
413
  if (groupId == null) {
418
414
  call.reject("Missing group identifier.");
@@ -462,8 +458,7 @@ public class CapacitorContactsPlugin extends Plugin {
462
458
 
463
459
  @PluginMethod
464
460
  public void displayContactById(PluginCall call) {
465
- JSObject options = call.getObject("options", new JSObject());
466
- String contactId = options.getString("id");
461
+ String contactId = call.getString("id");
467
462
 
468
463
  if (contactId == null) {
469
464
  call.reject("Missing contact identifier.");
@@ -485,12 +480,9 @@ public class CapacitorContactsPlugin extends Plugin {
485
480
  currentPickerCall = call;
486
481
  Intent intent = new Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI);
487
482
 
488
- JSObject options = call.getObject("options", new JSObject());
489
- if (options != null) {
490
- JSObject contactData = options.getJSObject("contact");
491
- if (contactData != null) {
492
- populateIntent(intent, contactData);
493
- }
483
+ JSObject contactData = call.getObject("contact");
484
+ if (contactData != null) {
485
+ populateIntent(intent, contactData);
494
486
  }
495
487
 
496
488
  startActivityForResult(call, intent, CREATE_CONTACT_REQUEST);
@@ -498,8 +490,7 @@ public class CapacitorContactsPlugin extends Plugin {
498
490
 
499
491
  @PluginMethod
500
492
  public void displayUpdateContactById(PluginCall call) {
501
- JSObject options = call.getObject("options", new JSObject());
502
- String contactId = options.getString("id");
493
+ String contactId = call.getString("id");
503
494
 
504
495
  if (contactId == null) {
505
496
  call.reject("Missing contact identifier.");
@@ -542,10 +533,10 @@ public class CapacitorContactsPlugin extends Plugin {
542
533
  if (data != null && data.getData() != null) {
543
534
  String contactId = getContactIdFromUri(data.getData());
544
535
  if (contactId != null) {
545
- ContactBuilder builder = fetchContact(contactId);
536
+ ContactBuilder builder = fetchContact(contactId, null);
546
537
  if (builder != null) {
547
538
  JSArray contacts = new JSArray();
548
- contacts.put(builder.toJSObject());
539
+ contacts.put(builder.toJSObject(null));
549
540
  call.resolve(new JSObject().put("contacts", contacts));
550
541
  } else {
551
542
  call.resolve(new JSObject().put("contacts", new JSArray()));
@@ -1061,8 +1052,8 @@ public class CapacitorContactsPlugin extends Plugin {
1061
1052
 
1062
1053
  // MARK: - Contact access helpers
1063
1054
 
1064
- private List<ContactBuilder> fetchContacts(Integer limit, Integer offset) {
1065
- List<ContactBuilder> builders = new ArrayList<>();
1055
+ private List<ContactBuilder> fetchContacts(Integer limit, Integer offset, Set<String> fields) {
1056
+ Map<String, ContactBuilder> builderMap = new java.util.LinkedHashMap<>();
1066
1057
  ContentResolver resolver = getContext().getContentResolver();
1067
1058
 
1068
1059
  Uri queryUri = ContactsContract.Contacts.CONTENT_URI;
@@ -1086,163 +1077,189 @@ public class CapacitorContactsPlugin extends Plugin {
1086
1077
  )
1087
1078
  ) {
1088
1079
  if (cursor == null) {
1089
- return builders;
1080
+ return new ArrayList<>();
1090
1081
  }
1091
1082
 
1092
1083
  while (cursor.moveToNext()) {
1093
1084
  String id = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID));
1094
- ContactBuilder contact = fetchContact(id);
1095
- if (contact != null) {
1096
- contact.fullName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
1097
- builders.add(contact);
1085
+ String displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
1086
+ ContactBuilder builder = new ContactBuilder(id);
1087
+ builder.fullName = displayName;
1088
+ builderMap.put(id, builder);
1089
+ }
1090
+ }
1091
+
1092
+ if (!builderMap.isEmpty()) {
1093
+ List<String> allIds = new ArrayList<>(builderMap.keySet());
1094
+
1095
+ for (int i = 0; i < allIds.size(); i += BATCH_SIZE) {
1096
+ List<String> batchIds = allIds.subList(i, Math.min(i + BATCH_SIZE, allIds.size()));
1097
+ fillContactData(builderMap, batchIds, fields);
1098
+ }
1099
+
1100
+ if (fields == null || fields.contains("account")) {
1101
+ for (int i = 0; i < allIds.size(); i += BATCH_SIZE) {
1102
+ List<String> batchIds = allIds.subList(i, Math.min(i + BATCH_SIZE, allIds.size()));
1103
+ fillAccountInfo(builderMap, batchIds);
1098
1104
  }
1099
1105
  }
1100
1106
  }
1101
1107
 
1102
- return builders;
1108
+ return new ArrayList<>(builderMap.values());
1103
1109
  }
1104
1110
 
1105
- private ContactBuilder fetchContact(@NonNull String contactId) {
1111
+ private void fillContactData(Map<String, ContactBuilder> builderMap, List<String> batchIds, Set<String> fields) {
1106
1112
  ContentResolver resolver = getContext().getContentResolver();
1107
- ContactBuilder builder = new ContactBuilder(contactId);
1113
+ List<String> selectionArgs = new ArrayList<>(batchIds);
1114
+ StringBuilder selection = new StringBuilder();
1115
+
1116
+ selection.append(ContactsContract.Data.CONTACT_ID + " IN (");
1117
+ for (int i = 0; i < batchIds.size(); i++) {
1118
+ if (i > 0) selection.append(",");
1119
+ selection.append("?");
1120
+ }
1121
+ selection.append(")");
1122
+
1123
+ if (fields != null) {
1124
+ List<String> mimeTypes = getMimeTypesForFields(fields);
1125
+
1126
+ if (!mimeTypes.isEmpty()) {
1127
+ StringBuilder mimeSelection = new StringBuilder();
1128
+ mimeSelection.append(" AND " + ContactsContract.Data.MIMETYPE + " IN (");
1129
+ for (int i = 0; i < mimeTypes.size(); i++) {
1130
+ if (i > 0) mimeSelection.append(",");
1131
+ mimeSelection.append("?");
1132
+ selectionArgs.add(mimeTypes.get(i));
1133
+ }
1134
+ mimeSelection.append(")");
1135
+ selection.append(mimeSelection.toString());
1136
+ }
1137
+ }
1108
1138
 
1109
- // Structured name, emails, phones, etc.
1110
1139
  try (
1111
1140
  Cursor dataCursor = resolver.query(
1112
1141
  ContactsContract.Data.CONTENT_URI,
1113
1142
  null,
1114
- ContactsContract.Data.CONTACT_ID + " = ?",
1115
- new String[] { contactId },
1143
+ selection.toString(),
1144
+ selectionArgs.toArray(new String[0]),
1116
1145
  null
1117
1146
  )
1118
1147
  ) {
1119
- if (dataCursor == null) {
1120
- return null;
1148
+ if (dataCursor != null) {
1149
+ while (dataCursor.moveToNext()) {
1150
+ String contactId = dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.Data.CONTACT_ID));
1151
+ ContactBuilder builder = builderMap.get(contactId);
1152
+ if (builder != null) {
1153
+ processDataRow(builder, dataCursor);
1154
+ }
1155
+ }
1121
1156
  }
1157
+ }
1158
+ }
1122
1159
 
1123
- while (dataCursor.moveToNext()) {
1124
- String mimeType = dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE));
1125
- switch (mimeType) {
1126
- case ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE:
1127
- builder.givenName = dataCursor.getString(
1128
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)
1129
- );
1130
- builder.familyName = dataCursor.getString(
1131
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)
1132
- );
1133
- builder.middleName = dataCursor.getString(
1134
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)
1135
- );
1136
- builder.namePrefix = dataCursor.getString(
1137
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.PREFIX)
1138
- );
1139
- builder.nameSuffix = dataCursor.getString(
1140
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.SUFFIX)
1141
- );
1142
- break;
1143
- case ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE:
1144
- builder.addEmail(
1145
- dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.ADDRESS)),
1146
- dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.TYPE)),
1147
- dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.LABEL)),
1148
- dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.IS_PRIMARY)) == 1
1149
- );
1150
- break;
1151
- case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:
1152
- builder.addPhone(
1153
- dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)),
1154
- dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE)),
1155
- dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL)),
1156
- dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.IS_PRIMARY)) == 1
1157
- );
1158
- break;
1159
- case ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE:
1160
- builder.addPostalAddress(
1161
- dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.TYPE)),
1162
- dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.LABEL)),
1163
- dataCursor.getString(
1164
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.STREET)
1165
- ),
1166
- dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.CITY)),
1167
- dataCursor.getString(
1168
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.REGION)
1169
- ),
1170
- dataCursor.getString(
1171
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE)
1172
- ),
1173
- dataCursor.getString(
1174
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY)
1175
- ),
1176
- dataCursor.getString(
1177
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD)
1178
- ),
1179
- dataCursor.getInt(
1180
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.IS_PRIMARY)
1181
- ) ==
1182
- 1
1183
- );
1184
- break;
1185
- case ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE:
1186
- builder.addUrlAddress(
1187
- dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Website.URL)),
1188
- dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Website.TYPE)),
1189
- dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Website.LABEL))
1190
- );
1191
- break;
1192
- case ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE:
1193
- builder.organizationName = dataCursor.getString(
1194
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Organization.COMPANY)
1195
- );
1196
- builder.jobTitle = dataCursor.getString(
1197
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Organization.TITLE)
1198
- );
1199
- break;
1200
- case ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE:
1201
- builder.note = dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Note.NOTE));
1202
- break;
1203
- case ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE:
1204
- int eventType = dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.TYPE));
1205
- if (eventType == ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) {
1206
- builder.setBirthday(
1207
- dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.START_DATE))
1208
- );
1209
- }
1210
- break;
1211
- case ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE:
1212
- long groupId = dataCursor.getLong(
1213
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID)
1160
+ private void fillAccountInfo(Map<String, ContactBuilder> builderMap, List<String> batchIds) {
1161
+ ContentResolver resolver = getContext().getContentResolver();
1162
+ List<String> selectionArgs = new ArrayList<>(batchIds);
1163
+ StringBuilder selection = new StringBuilder();
1164
+
1165
+ selection.append(ContactsContract.RawContacts.CONTACT_ID + " IN (");
1166
+ for (int i = 0; i < batchIds.size(); i++) {
1167
+ if (i > 0) selection.append(",");
1168
+ selection.append("?");
1169
+ }
1170
+ selection.append(")");
1171
+
1172
+ try (
1173
+ Cursor rawCursor = resolver.query(
1174
+ ContactsContract.RawContacts.CONTENT_URI,
1175
+ new String[] {
1176
+ ContactsContract.RawContacts.CONTACT_ID,
1177
+ ContactsContract.RawContacts.ACCOUNT_NAME,
1178
+ ContactsContract.RawContacts.ACCOUNT_TYPE
1179
+ },
1180
+ selection.toString(),
1181
+ selectionArgs.toArray(new String[0]),
1182
+ null
1183
+ )
1184
+ ) {
1185
+ if (rawCursor != null) {
1186
+ while (rawCursor.moveToNext()) {
1187
+ String contactId = rawCursor.getString(rawCursor.getColumnIndexOrThrow(ContactsContract.RawContacts.CONTACT_ID));
1188
+ ContactBuilder builder = builderMap.get(contactId);
1189
+ if (builder != null && builder.accountName == null) {
1190
+ builder.accountName = rawCursor.getString(
1191
+ rawCursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_NAME)
1214
1192
  );
1215
- builder.addGroupId(String.valueOf(groupId));
1216
- break;
1217
- case ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE:
1218
- byte[] photoData = dataCursor.getBlob(
1219
- dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Photo.PHOTO)
1193
+ builder.accountType = rawCursor.getString(
1194
+ rawCursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_TYPE)
1220
1195
  );
1221
- if (photoData != null) {
1222
- builder.photoBase64 = Base64.encodeToString(photoData, Base64.NO_WRAP);
1223
- }
1224
- break;
1225
- default:
1226
- break;
1196
+ }
1197
+ }
1198
+ }
1199
+ }
1200
+ }
1201
+
1202
+ private ContactBuilder fetchContact(@NonNull String contactId, Set<String> fields) {
1203
+ ContentResolver resolver = getContext().getContentResolver();
1204
+ ContactBuilder builder = new ContactBuilder(contactId);
1205
+
1206
+ // Build selection based on fields
1207
+ String selection = ContactsContract.Data.CONTACT_ID + " = ?";
1208
+ List<String> selectionArgs = new ArrayList<>();
1209
+ selectionArgs.add(contactId);
1210
+
1211
+ if (fields != null) {
1212
+ List<String> mimeTypes = getMimeTypesForFields(fields);
1213
+
1214
+ if (!mimeTypes.isEmpty()) {
1215
+ StringBuilder mimeSelection = new StringBuilder();
1216
+ mimeSelection.append(" AND " + ContactsContract.Data.MIMETYPE + " IN (");
1217
+ for (int i = 0; i < mimeTypes.size(); i++) {
1218
+ if (i > 0) mimeSelection.append(",");
1219
+ mimeSelection.append("?");
1220
+ selectionArgs.add(mimeTypes.get(i));
1227
1221
  }
1222
+ mimeSelection.append(")");
1223
+ selection += mimeSelection.toString();
1228
1224
  }
1229
1225
  }
1230
1226
 
1231
- // Account information
1227
+ // Structured name, emails, phones, etc.
1232
1228
  try (
1233
- Cursor rawCursor = resolver.query(
1234
- ContactsContract.RawContacts.CONTENT_URI,
1235
- new String[] { ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE },
1236
- ContactsContract.RawContacts.CONTACT_ID + " = ?",
1237
- new String[] { contactId },
1229
+ Cursor dataCursor = resolver.query(
1230
+ ContactsContract.Data.CONTENT_URI,
1231
+ null,
1232
+ selection,
1233
+ selectionArgs.toArray(new String[0]),
1238
1234
  null
1239
1235
  )
1240
1236
  ) {
1241
- if (rawCursor != null && rawCursor.moveToFirst()) {
1242
- String accountName = rawCursor.getString(rawCursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_NAME));
1243
- String accountType = rawCursor.getString(rawCursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_TYPE));
1244
- builder.accountName = accountName;
1245
- builder.accountType = accountType;
1237
+ if (dataCursor == null) {
1238
+ return null;
1239
+ }
1240
+
1241
+ while (dataCursor.moveToNext()) {
1242
+ processDataRow(builder, dataCursor);
1243
+ }
1244
+ }
1245
+
1246
+ // Account information (only if fields allow)
1247
+ if (fields == null || fields.contains("account")) {
1248
+ try (
1249
+ Cursor rawCursor = resolver.query(
1250
+ ContactsContract.RawContacts.CONTENT_URI,
1251
+ new String[] { ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE },
1252
+ ContactsContract.RawContacts.CONTACT_ID + " = ?",
1253
+ new String[] { contactId },
1254
+ null
1255
+ )
1256
+ ) {
1257
+ if (rawCursor != null && rawCursor.moveToFirst()) {
1258
+ String accountName = rawCursor.getString(rawCursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_NAME));
1259
+ String accountType = rawCursor.getString(rawCursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_TYPE));
1260
+ builder.accountName = accountName;
1261
+ builder.accountType = accountType;
1262
+ }
1246
1263
  }
1247
1264
  }
1248
1265
 
@@ -1253,6 +1270,98 @@ public class CapacitorContactsPlugin extends Plugin {
1253
1270
  return builder;
1254
1271
  }
1255
1272
 
1273
+ private void processDataRow(ContactBuilder builder, Cursor dataCursor) {
1274
+ String mimeType = dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE));
1275
+ switch (mimeType) {
1276
+ case ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE:
1277
+ builder.givenName = dataCursor.getString(
1278
+ dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)
1279
+ );
1280
+ builder.familyName = dataCursor.getString(
1281
+ dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)
1282
+ );
1283
+ builder.middleName = dataCursor.getString(
1284
+ dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)
1285
+ );
1286
+ builder.namePrefix = dataCursor.getString(
1287
+ dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.PREFIX)
1288
+ );
1289
+ builder.nameSuffix = dataCursor.getString(
1290
+ dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.SUFFIX)
1291
+ );
1292
+ break;
1293
+ case ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE:
1294
+ builder.addEmail(
1295
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.ADDRESS)),
1296
+ dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.TYPE)),
1297
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.LABEL)),
1298
+ dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.IS_PRIMARY)) == 1
1299
+ );
1300
+ break;
1301
+ case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:
1302
+ builder.addPhone(
1303
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)),
1304
+ dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE)),
1305
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL)),
1306
+ dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.IS_PRIMARY)) == 1
1307
+ );
1308
+ break;
1309
+ case ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE:
1310
+ builder.addPostalAddress(
1311
+ dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.TYPE)),
1312
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.LABEL)),
1313
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.STREET)),
1314
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.CITY)),
1315
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.REGION)),
1316
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE)),
1317
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY)),
1318
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD)),
1319
+ dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.IS_PRIMARY)) == 1
1320
+ );
1321
+ break;
1322
+ case ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE:
1323
+ builder.addUrlAddress(
1324
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Website.URL)),
1325
+ dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Website.TYPE)),
1326
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Website.LABEL))
1327
+ );
1328
+ break;
1329
+ case ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE:
1330
+ builder.organizationName = dataCursor.getString(
1331
+ dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Organization.COMPANY)
1332
+ );
1333
+ builder.jobTitle = dataCursor.getString(
1334
+ dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Organization.TITLE)
1335
+ );
1336
+ break;
1337
+ case ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE:
1338
+ builder.note = dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Note.NOTE));
1339
+ break;
1340
+ case ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE:
1341
+ int eventType = dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.TYPE));
1342
+ if (eventType == ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) {
1343
+ builder.setBirthday(
1344
+ dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.START_DATE))
1345
+ );
1346
+ }
1347
+ break;
1348
+ case ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE:
1349
+ long groupId = dataCursor.getLong(
1350
+ dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID)
1351
+ );
1352
+ builder.addGroupId(String.valueOf(groupId));
1353
+ break;
1354
+ case ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE:
1355
+ byte[] photoData = dataCursor.getBlob(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Photo.PHOTO));
1356
+ if (photoData != null) {
1357
+ builder.photoBase64 = Base64.encodeToString(photoData, Base64.NO_WRAP);
1358
+ }
1359
+ break;
1360
+ default:
1361
+ break;
1362
+ }
1363
+ }
1364
+
1256
1365
  private String resolveDisplayName(String contactId) {
1257
1366
  ContentResolver resolver = getContext().getContentResolver();
1258
1367
  try (
@@ -1271,6 +1380,65 @@ public class CapacitorContactsPlugin extends Plugin {
1271
1380
  return null;
1272
1381
  }
1273
1382
 
1383
+ private Set<String> parseFieldsArray(PluginCall call) {
1384
+ JSArray fieldsArray = call.getArray("fields", null);
1385
+ if (fieldsArray == null) {
1386
+ return null;
1387
+ }
1388
+ Set<String> fields = new HashSet<>();
1389
+ try {
1390
+ List<Object> list = fieldsArray.toList();
1391
+ for (Object item : list) {
1392
+ if (item instanceof String) {
1393
+ fields.add((String) item);
1394
+ }
1395
+ }
1396
+ } catch (Exception e) {
1397
+ // Return empty set if parsing fails
1398
+ }
1399
+ return fields.isEmpty() ? null : fields;
1400
+ }
1401
+
1402
+ private List<String> getMimeTypesForFields(Set<String> fields) {
1403
+ List<String> mimeTypes = new ArrayList<>();
1404
+ // Always fetch structured name for basic display
1405
+ mimeTypes.add(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
1406
+
1407
+ if (fields == null) {
1408
+ return mimeTypes;
1409
+ }
1410
+
1411
+ if (fields.contains("organizationName") || fields.contains("jobTitle")) {
1412
+ mimeTypes.add(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE);
1413
+ }
1414
+ if (fields.contains("emailAddresses")) {
1415
+ mimeTypes.add(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
1416
+ }
1417
+ if (fields.contains("phoneNumbers")) {
1418
+ mimeTypes.add(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
1419
+ }
1420
+ if (fields.contains("postalAddresses")) {
1421
+ mimeTypes.add(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE);
1422
+ }
1423
+ if (fields.contains("urlAddresses")) {
1424
+ mimeTypes.add(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE);
1425
+ }
1426
+ if (fields.contains("note")) {
1427
+ mimeTypes.add(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE);
1428
+ }
1429
+ if (fields.contains("birthday")) {
1430
+ mimeTypes.add(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE);
1431
+ }
1432
+ if (fields.contains("groupIds")) {
1433
+ mimeTypes.add(ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE);
1434
+ }
1435
+ if (fields.contains("photo")) {
1436
+ mimeTypes.add(ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE);
1437
+ }
1438
+
1439
+ return mimeTypes;
1440
+ }
1441
+
1274
1442
  // MARK: - Contact builder helper
1275
1443
 
1276
1444
  private static class ContactBuilder {
@@ -1381,36 +1549,50 @@ public class CapacitorContactsPlugin extends Plugin {
1381
1549
  if (startDate == null || startDate.isEmpty()) {
1382
1550
  return;
1383
1551
  }
1384
- String[] parts = startDate.split("-");
1385
- if (parts.length >= 2) {
1386
- birthdayMonth = safeParse(parts[0]);
1387
- birthdayDay = safeParse(parts[1]);
1388
- }
1389
- if (parts.length >= 3) {
1390
- birthdayYear = safeParse(parts[2]);
1552
+ // Handle yearless dates: --MM-DD
1553
+ if (startDate.startsWith("--")) {
1554
+ String[] parts = startDate.substring(2).split("-");
1555
+ if (parts.length >= 2) {
1556
+ birthdayMonth = safeParse(parts[0]);
1557
+ birthdayDay = safeParse(parts[1]);
1558
+ }
1559
+ } else {
1560
+ // Standard format: YYYY-MM-DD
1561
+ String[] parts = startDate.split("-");
1562
+ if (parts.length >= 3) {
1563
+ birthdayYear = safeParse(parts[0]);
1564
+ birthdayMonth = safeParse(parts[1]);
1565
+ birthdayDay = safeParse(parts[2]);
1566
+ }
1391
1567
  }
1392
1568
  }
1393
1569
 
1394
1570
  JSObject toJSObject() {
1571
+ return toJSObject(null);
1572
+ }
1573
+
1574
+ JSObject toJSObject(Set<String> fields) {
1575
+ boolean includeAll = fields == null;
1395
1576
  JSObject contact = new JSObject();
1396
- contact.put("id", id);
1397
- contact.put("givenName", givenName);
1398
- contact.put("familyName", familyName);
1399
- contact.put("middleName", middleName);
1400
- contact.put("namePrefix", namePrefix);
1401
- contact.put("nameSuffix", nameSuffix);
1402
- contact.put("organizationName", organizationName);
1403
- contact.put("jobTitle", jobTitle);
1404
- contact.put("note", note);
1405
- contact.put("fullName", fullName);
1406
- contact.put("photo", photoBase64);
1407
- contact.put("groupIds", groupIds);
1408
- contact.put("emailAddresses", emailAddresses);
1409
- contact.put("phoneNumbers", phoneNumbers);
1410
- contact.put("postalAddresses", postalAddresses);
1411
- contact.put("urlAddresses", urlAddresses);
1412
-
1413
- if (birthdayYear != null || birthdayMonth != null || birthdayDay != null) {
1577
+
1578
+ if (includeAll || fields.contains("id")) contact.put("id", id);
1579
+ if (includeAll || fields.contains("givenName")) contact.put("givenName", givenName);
1580
+ if (includeAll || fields.contains("familyName")) contact.put("familyName", familyName);
1581
+ if (includeAll || fields.contains("middleName")) contact.put("middleName", middleName);
1582
+ if (includeAll || fields.contains("namePrefix")) contact.put("namePrefix", namePrefix);
1583
+ if (includeAll || fields.contains("nameSuffix")) contact.put("nameSuffix", nameSuffix);
1584
+ if (includeAll || fields.contains("organizationName")) contact.put("organizationName", organizationName);
1585
+ if (includeAll || fields.contains("jobTitle")) contact.put("jobTitle", jobTitle);
1586
+ if (includeAll || fields.contains("note")) contact.put("note", note);
1587
+ if (includeAll || fields.contains("fullName")) contact.put("fullName", fullName);
1588
+ if (includeAll || fields.contains("photo")) contact.put("photo", photoBase64);
1589
+ if (includeAll || fields.contains("groupIds")) contact.put("groupIds", groupIds);
1590
+ if (includeAll || fields.contains("emailAddresses")) contact.put("emailAddresses", emailAddresses);
1591
+ if (includeAll || fields.contains("phoneNumbers")) contact.put("phoneNumbers", phoneNumbers);
1592
+ if (includeAll || fields.contains("postalAddresses")) contact.put("postalAddresses", postalAddresses);
1593
+ if (includeAll || fields.contains("urlAddresses")) contact.put("urlAddresses", urlAddresses);
1594
+
1595
+ if ((includeAll || fields.contains("birthday")) && (birthdayYear != null || birthdayMonth != null || birthdayDay != null)) {
1414
1596
  JSObject birthday = new JSObject();
1415
1597
  if (birthdayDay != null) birthday.put("day", birthdayDay);
1416
1598
  if (birthdayMonth != null) birthday.put("month", birthdayMonth);
@@ -1418,13 +1600,15 @@ public class CapacitorContactsPlugin extends Plugin {
1418
1600
  contact.put("birthday", birthday);
1419
1601
  }
1420
1602
 
1421
- if (accountName != null || accountType != null) {
1422
- JSObject account = new JSObject();
1423
- account.put("name", accountName);
1424
- account.put("type", accountType);
1425
- contact.put("account", account);
1426
- } else {
1427
- contact.put("account", null);
1603
+ if (includeAll || fields.contains("account")) {
1604
+ if (accountName != null || accountType != null) {
1605
+ JSObject account = new JSObject();
1606
+ account.put("name", accountName);
1607
+ account.put("type", accountType);
1608
+ contact.put("account", account);
1609
+ } else {
1610
+ contact.put("account", null);
1611
+ }
1428
1612
  }
1429
1613
 
1430
1614
  return contact;
@@ -5,7 +5,7 @@ import UIKit
5
5
 
6
6
  @objc(CapacitorContactsPlugin)
7
7
  public class CapacitorContactsPlugin: CAPPlugin, CAPBridgedPlugin {
8
- private let pluginVersion: String = "8.0.4"
8
+ private let pluginVersion: String = "8.0.5"
9
9
  public let identifier = "CapacitorContactsPlugin"
10
10
  public let jsName = "CapacitorContacts"
11
11
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-contacts",
3
- "version": "8.0.4",
3
+ "version": "8.0.5",
4
4
  "description": "Work with device contacts using Capacitor APIs",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",