expo-contacts 12.6.0 → 12.7.0

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 (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/android/build.gradle +4 -4
  3. package/android/src/main/java/expo/modules/contacts/Columns.kt +26 -0
  4. package/android/src/main/java/expo/modules/contacts/CommonProvider.kt +8 -0
  5. package/android/src/main/java/expo/modules/contacts/Contact.kt +516 -0
  6. package/android/src/main/java/expo/modules/contacts/ContactsModule.kt +682 -0
  7. package/android/src/main/java/expo/modules/contacts/models/BaseModel.kt +146 -0
  8. package/android/src/main/java/expo/modules/contacts/models/DateModel.kt +56 -0
  9. package/android/src/main/java/expo/modules/contacts/models/EmailModel.kt +22 -0
  10. package/android/src/main/java/expo/modules/contacts/models/ExtraNameModel.kt +34 -0
  11. package/android/src/main/java/expo/modules/contacts/models/ImAddressModel.kt +39 -0
  12. package/android/src/main/java/expo/modules/contacts/models/PhoneNumberModel.kt +55 -0
  13. package/android/src/main/java/expo/modules/contacts/models/PostalAddressModel.kt +78 -0
  14. package/android/src/main/java/expo/modules/contacts/models/RelationshipModel.kt +32 -0
  15. package/android/src/main/java/expo/modules/contacts/models/UrlAddressModel.kt +25 -0
  16. package/build/Contacts.d.ts +4 -0
  17. package/build/Contacts.d.ts.map +1 -1
  18. package/build/Contacts.js.map +1 -1
  19. package/build/ExpoContacts.d.ts +1 -1
  20. package/build/ExpoContacts.d.ts.map +1 -1
  21. package/build/ExpoContacts.js +2 -2
  22. package/build/ExpoContacts.js.map +1 -1
  23. package/expo-module.config.json +6 -0
  24. package/ios/EXContacts.podspec +1 -1
  25. package/package.json +2 -2
  26. package/src/Contacts.ts +4 -0
  27. package/src/ExpoContacts.ts +2 -2
  28. package/android/src/main/java/expo/modules/contacts/CommonProvider.java +0 -9
  29. package/android/src/main/java/expo/modules/contacts/Contact.java +0 -577
  30. package/android/src/main/java/expo/modules/contacts/ContactsModule.java +0 -870
  31. package/android/src/main/java/expo/modules/contacts/ContactsPackage.java +0 -17
  32. package/android/src/main/java/expo/modules/contacts/EXColumns.java +0 -27
  33. package/android/src/main/java/expo/modules/contacts/models/BaseModel.java +0 -202
  34. package/android/src/main/java/expo/modules/contacts/models/DateModel.java +0 -85
  35. package/android/src/main/java/expo/modules/contacts/models/EmailModel.java +0 -37
  36. package/android/src/main/java/expo/modules/contacts/models/ExtraNameModel.java +0 -68
  37. package/android/src/main/java/expo/modules/contacts/models/ImAddressModel.java +0 -60
  38. package/android/src/main/java/expo/modules/contacts/models/PhoneNumberModel.java +0 -122
  39. package/android/src/main/java/expo/modules/contacts/models/PostalAddressModel.java +0 -108
  40. package/android/src/main/java/expo/modules/contacts/models/RelationshipModel.java +0 -60
  41. package/android/src/main/java/expo/modules/contacts/models/UrlAddressModel.java +0 -45
  42. package/unimodule.json +0 -4
package/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 12.7.0 — 2023-11-14
14
+
15
+ ### 🛠 Breaking changes
16
+
17
+ - Bumped iOS deployment target to 13.4. ([#25063](https://github.com/expo/expo/pull/25063) by [@gabrieldonadel](https://github.com/gabrieldonadel))
18
+ - On `Android` bump `compileSdkVersion` and `targetSdkVersion` to `34`. ([#24708](https://github.com/expo/expo/pull/24708) by [@alanjhughes](https://github.com/alanjhughes))
19
+
20
+ ### 💡 Others
21
+
22
+ - Renamed `unimodule.json` to `expo-module.config.json`. ([#25100](https://github.com/expo/expo/pull/25100) by [@reichhartd](https://github.com/reichhartd))
23
+ - Migrated codebase to use Expo Modules API. ([#24991](https://github.com/expo/expo/pull/24991) by [@lukmccall](https://github.com/lukmccall))
24
+
13
25
  ## 12.6.0 — 2023-10-17
14
26
 
15
27
  ### 🛠 Breaking changes
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven-publish'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '12.6.0'
6
+ version = '12.7.0'
7
7
 
8
8
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
9
9
  if (expoModulesCorePlugin.exists()) {
@@ -61,11 +61,11 @@ if (!safeExtGet("expoProvidesDefaultConfig", false)) {
61
61
  android {
62
62
  // Remove this if and it's contents, when support for SDK49 is dropped
63
63
  if (!safeExtGet("expoProvidesDefaultConfig", false)) {
64
- compileSdkVersion safeExtGet("compileSdkVersion", 33)
64
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
65
65
 
66
66
  defaultConfig {
67
67
  minSdkVersion safeExtGet("minSdkVersion", 23)
68
- targetSdkVersion safeExtGet("targetSdkVersion", 33)
68
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
69
69
  }
70
70
 
71
71
  publishing {
@@ -94,7 +94,7 @@ android {
94
94
  namespace "expo.modules.contacts"
95
95
  defaultConfig {
96
96
  versionCode 29
97
- versionName "12.6.0"
97
+ versionName "12.7.0"
98
98
  }
99
99
 
100
100
  }
@@ -0,0 +1,26 @@
1
+ package expo.modules.contacts
2
+
3
+ import android.provider.ContactsContract
4
+
5
+ object Columns {
6
+ const val DATA_10 = "data10"
7
+ const val DATA_9 = "data9"
8
+ const val DATA_8 = "data8"
9
+ const val DATA_7 = "data7"
10
+ const val DATA_6 = "data6"
11
+ const val DATA_5 = "data5"
12
+ const val DATA_4 = "data4"
13
+ const val LABEL = "data3"
14
+ const val TYPE = "data2"
15
+ const val DATA = "data1"
16
+ const val ID = ContactsContract.Data._ID
17
+ const val IS_PRIMARY = ContactsContract.Data.IS_PRIMARY
18
+ const val CONTACT_ID = ContactsContract.Data.CONTACT_ID
19
+ const val LOOKUP_KEY = ContactsContract.Data.LOOKUP_KEY
20
+ const val DISPLAY_NAME = ContactsContract.Data.DISPLAY_NAME
21
+ const val PHOTO_URI = ContactsContract.CommonDataKinds.Contactables.PHOTO_URI
22
+ const val PHOTO_THUMBNAIL_URI = ContactsContract.CommonDataKinds.Contactables.PHOTO_THUMBNAIL_URI
23
+ const val IS_USER_PROFILE = ContactsContract.CommonDataKinds.Contactables.IS_USER_PROFILE
24
+ const val MIMETYPE = ContactsContract.Data.MIMETYPE
25
+ const val TYPE_CUSTOM = 0
26
+ }
@@ -0,0 +1,8 @@
1
+ package expo.modules.contacts
2
+
3
+ interface CommonProvider {
4
+ val contentType: String
5
+ val dataAlias: String
6
+ val labelAlias: String
7
+ val idAlias: String
8
+ }
@@ -0,0 +1,516 @@
1
+ package expo.modules.contacts
2
+
3
+ import android.content.ContentProviderOperation
4
+ import android.content.ContentValues
5
+ import android.database.Cursor
6
+ import android.graphics.Bitmap
7
+ import android.graphics.BitmapFactory
8
+ import android.net.Uri
9
+ import android.os.Bundle
10
+ import android.provider.ContactsContract
11
+ import android.provider.ContactsContract.CommonDataKinds
12
+ import android.provider.ContactsContract.CommonDataKinds.StructuredName
13
+ import android.provider.ContactsContract.RawContacts
14
+ import android.text.TextUtils
15
+ import android.util.Log
16
+ import expo.modules.contacts.models.BaseModel
17
+ import expo.modules.contacts.models.DateModel
18
+ import expo.modules.contacts.models.EmailModel
19
+ import expo.modules.contacts.models.ExtraNameModel
20
+ import expo.modules.contacts.models.ImAddressModel
21
+ import expo.modules.contacts.models.PhoneNumberModel
22
+ import expo.modules.contacts.models.PostalAddressModel
23
+ import expo.modules.contacts.models.RelationshipModel
24
+ import expo.modules.contacts.models.UrlAddressModel
25
+ import java.io.ByteArrayOutputStream
26
+ import java.text.ParseException
27
+ import java.text.SimpleDateFormat
28
+ import java.util.Calendar
29
+ import java.util.Locale
30
+
31
+ // TODO: MaidenName Nickname
32
+ class Contact(var contactId: String) {
33
+ private var rawContactId: String? = null
34
+ var lookupKey: String? = null
35
+ private var displayName: String? = null
36
+ var hasPhoto = false
37
+ var photoUri: String? = null
38
+ private var rawPhotoUri: String? = null
39
+ private var contactType = "person"
40
+ var firstName: String? = ""
41
+ var middleName: String? = ""
42
+ var lastName: String? = ""
43
+
44
+ var prefix: String? = ""
45
+ var suffix: String? = ""
46
+ var phoneticFirstName: String? = ""
47
+ var phoneticMiddleName: String? = ""
48
+ var phoneticLastName: String? = ""
49
+ var company: String? = ""
50
+ var department: String? = ""
51
+ var jobTitle: String? = ""
52
+ var note: String? = null
53
+ var dates: MutableList<DateModel> = ArrayList()
54
+ var emails: MutableList<EmailModel> = ArrayList()
55
+ var imAddresses: MutableList<ImAddressModel> = ArrayList()
56
+ var phones: MutableList<PhoneNumberModel> = ArrayList()
57
+ var addresses: MutableList<PostalAddressModel> = ArrayList()
58
+ var relationships: MutableList<RelationshipModel> = ArrayList()
59
+ var urlAddresses: MutableList<UrlAddressModel> = ArrayList()
60
+ var extraNames: MutableList<ExtraNameModel> = ArrayList()
61
+
62
+ fun fromCursor(cursor: Cursor) {
63
+ rawContactId = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.RAW_CONTACT_ID))
64
+ val mimeType = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE))
65
+ val name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME))
66
+ if (!TextUtils.isEmpty(name) && TextUtils.isEmpty(displayName)) {
67
+ displayName = name
68
+ }
69
+ if (TextUtils.isEmpty(rawPhotoUri)) {
70
+ val rawPhotoURI = cursor.getString(cursor.getColumnIndexOrThrow(Columns.PHOTO_URI))
71
+ if (!TextUtils.isEmpty(rawPhotoURI)) {
72
+ hasPhoto = true
73
+ rawPhotoUri = rawPhotoURI
74
+ }
75
+ }
76
+ if (TextUtils.isEmpty(photoUri)) {
77
+ val rawPhotoURI = cursor.getString(cursor.getColumnIndexOrThrow(Columns.PHOTO_THUMBNAIL_URI))
78
+ if (!TextUtils.isEmpty(rawPhotoURI)) {
79
+ hasPhoto = true
80
+ photoUri = rawPhotoURI
81
+ }
82
+ }
83
+ when (mimeType) {
84
+ StructuredName.CONTENT_ITEM_TYPE -> {
85
+ lookupKey = cursor.getString(cursor.getColumnIndexOrThrow(StructuredName.LOOKUP_KEY))
86
+ firstName = cursor.getString(cursor.getColumnIndexOrThrow(StructuredName.GIVEN_NAME))
87
+ middleName = cursor.getString(cursor.getColumnIndexOrThrow(StructuredName.MIDDLE_NAME))
88
+ lastName = cursor.getString(cursor.getColumnIndexOrThrow(StructuredName.FAMILY_NAME))
89
+ prefix = cursor.getString(cursor.getColumnIndexOrThrow(StructuredName.PREFIX))
90
+ suffix = cursor.getString(cursor.getColumnIndexOrThrow(StructuredName.SUFFIX))
91
+ phoneticFirstName = cursor
92
+ .getString(cursor.getColumnIndexOrThrow(StructuredName.PHONETIC_GIVEN_NAME))
93
+ phoneticMiddleName = cursor
94
+ .getString(cursor.getColumnIndexOrThrow(StructuredName.PHONETIC_MIDDLE_NAME))
95
+ phoneticLastName = cursor
96
+ .getString(cursor.getColumnIndexOrThrow(StructuredName.PHONETIC_FAMILY_NAME))
97
+ }
98
+
99
+ CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> {
100
+ company = cursor.getString(cursor.getColumnIndexOrThrow(CommonDataKinds.Organization.COMPANY))
101
+ jobTitle = cursor.getString(cursor.getColumnIndexOrThrow(CommonDataKinds.Organization.TITLE))
102
+ department = cursor.getString(cursor.getColumnIndexOrThrow(CommonDataKinds.Organization.DEPARTMENT))
103
+ }
104
+
105
+ CommonDataKinds.Note.CONTENT_ITEM_TYPE -> {
106
+ note = cursor.getString(cursor.getColumnIndexOrThrow(CommonDataKinds.Note.NOTE))
107
+ }
108
+
109
+ CommonDataKinds.Event.CONTENT_ITEM_TYPE -> {
110
+ val item = DateModel()
111
+ item.fromCursor(cursor)
112
+ dates.add(item)
113
+ }
114
+
115
+ CommonDataKinds.Email.CONTENT_ITEM_TYPE -> {
116
+ val item = EmailModel()
117
+ item.fromCursor(cursor)
118
+ emails.add(item)
119
+ }
120
+
121
+ CommonDataKinds.Im.CONTENT_ITEM_TYPE -> {
122
+ val item = ImAddressModel()
123
+ item.fromCursor(cursor)
124
+ imAddresses.add(item)
125
+ }
126
+
127
+ CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
128
+ val item = PhoneNumberModel()
129
+ item.fromCursor(cursor)
130
+ phones.add(item)
131
+ }
132
+
133
+ CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE -> {
134
+ val item = PostalAddressModel()
135
+ item.fromCursor(cursor)
136
+ addresses.add(item)
137
+ }
138
+
139
+ CommonDataKinds.Relation.CONTENT_ITEM_TYPE -> {
140
+ val item = RelationshipModel()
141
+ item.fromCursor(cursor)
142
+ relationships.add(item)
143
+ }
144
+
145
+ CommonDataKinds.Website.CONTENT_ITEM_TYPE -> {
146
+ val urlAddressModel = UrlAddressModel()
147
+ urlAddressModel.fromCursor(cursor)
148
+ urlAddresses.add(urlAddressModel)
149
+ }
150
+
151
+ CommonDataKinds.Nickname.CONTENT_ITEM_TYPE -> {
152
+ val item = ExtraNameModel()
153
+ item.fromCursor(cursor)
154
+ extraNames.add(item)
155
+ }
156
+ }
157
+ val hasCompanyName = company != null && company != ""
158
+ contactType = if (hasCompanyName) {
159
+ val hasFirstName = firstName != null && firstName != ""
160
+ val hasMiddleName = middleName != null && middleName != ""
161
+ val hasLastName = lastName != null && lastName != ""
162
+ if (!hasFirstName && !hasMiddleName && !hasLastName) {
163
+ "company"
164
+ } else {
165
+ "person"
166
+ }
167
+ } else {
168
+ "person"
169
+ }
170
+ }
171
+
172
+ fun getFinalFirstName(): String {
173
+ return firstName ?: if (displayName == null) "" else displayName!!
174
+ }
175
+
176
+ fun getFinalLastName(): String {
177
+ return lastName ?: if (displayName == null) "" else displayName!!
178
+ }
179
+
180
+ fun getFinalDisplayName(): String? {
181
+ return if (displayName == null && firstName != null) {
182
+ if (lastName == null) firstName else String.format("%s %s", firstName, lastName).trim { it <= ' ' }
183
+ } else displayName
184
+ }
185
+
186
+ private fun toByteArray(bitmap: Bitmap): ByteArray {
187
+ val stream = ByteArrayOutputStream()
188
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 80, stream)
189
+ return stream.toByteArray()
190
+ }
191
+
192
+ fun toInsertOperationList(): ArrayList<ContentProviderOperation> {
193
+ val ops = ArrayList<ContentProviderOperation>()
194
+ var op = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
195
+ .withValue(RawContacts.ACCOUNT_TYPE, null)
196
+ .withValue(RawContacts.ACCOUNT_NAME, null)
197
+ ops.add(op.build())
198
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
199
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
200
+ .withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
201
+ .withValue(StructuredName.DISPLAY_NAME, displayName)
202
+ .withValue(StructuredName.GIVEN_NAME, firstName)
203
+ .withValue(StructuredName.MIDDLE_NAME, middleName)
204
+ .withValue(StructuredName.FAMILY_NAME, lastName)
205
+ .withValue(StructuredName.PHONETIC_GIVEN_NAME, phoneticFirstName)
206
+ .withValue(StructuredName.PHONETIC_MIDDLE_NAME, phoneticMiddleName)
207
+ .withValue(StructuredName.PHONETIC_FAMILY_NAME, phoneticLastName)
208
+ .withValue(StructuredName.PREFIX, prefix)
209
+ .withValue(StructuredName.SUFFIX, suffix)
210
+ ops.add(op.build())
211
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
212
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
213
+ .withValue(Columns.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
214
+ .withValue(CommonDataKinds.Organization.COMPANY, company)
215
+ .withValue(CommonDataKinds.Organization.TITLE, jobTitle)
216
+ .withValue(CommonDataKinds.Organization.DEPARTMENT, department)
217
+ ops.add(op.build())
218
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
219
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
220
+ .withValue(Columns.MIMETYPE, CommonDataKinds.Note.CONTENT_ITEM_TYPE)
221
+ .withValue(CommonDataKinds.Note.NOTE, note)
222
+ ops.add(op.build())
223
+ op.withYieldAllowed(true)
224
+ if (!TextUtils.isEmpty(photoUri) || !TextUtils.isEmpty(rawPhotoUri)) {
225
+ val photo = getThumbnailBitmap(if (TextUtils.isEmpty(rawPhotoUri)) photoUri else rawPhotoUri)
226
+ ops.add(
227
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
228
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
229
+ .withValue(Columns.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
230
+ .withValue(CommonDataKinds.Photo.PHOTO, toByteArray(photo))
231
+ .build()
232
+ )
233
+ }
234
+ for (map in baseModels) {
235
+ for (item in map) {
236
+ ops.add(item.insertOperation)
237
+ }
238
+ }
239
+ return ops
240
+ }
241
+
242
+ fun toUpdateOperationList(): ArrayList<ContentProviderOperation?> {
243
+ val selection = String.format("%s=? AND %s=?", ContactsContract.Data.CONTACT_ID, ContactsContract.Data.MIMETYPE)
244
+ val selectionArgs = arrayOf(contactId, StructuredName.CONTENT_ITEM_TYPE)
245
+ val ops = ArrayList<ContentProviderOperation?>()
246
+ var op: ContentProviderOperation.Builder = ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
247
+ .withSelection(selection, selectionArgs)
248
+ .withValue(StructuredName.DISPLAY_NAME, displayName)
249
+ .withValue(StructuredName.GIVEN_NAME, firstName)
250
+ .withValue(StructuredName.MIDDLE_NAME, middleName)
251
+ .withValue(StructuredName.FAMILY_NAME, lastName)
252
+ .withValue(StructuredName.PHONETIC_GIVEN_NAME, phoneticFirstName)
253
+ .withValue(StructuredName.PHONETIC_MIDDLE_NAME, phoneticMiddleName)
254
+ .withValue(StructuredName.PHONETIC_FAMILY_NAME, phoneticLastName)
255
+ .withValue(StructuredName.PREFIX, prefix).withValue(StructuredName.SUFFIX, suffix)
256
+ ops.add(op.build())
257
+ op = ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
258
+ .withSelection(selection, selectionArgs)
259
+ .withValue(CommonDataKinds.Organization.COMPANY, company)
260
+ .withValue(CommonDataKinds.Organization.TITLE, jobTitle)
261
+ .withValue(CommonDataKinds.Organization.DEPARTMENT, department)
262
+ ops.add(op.build())
263
+ op = ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
264
+ .withSelection(selection, arrayOf(contactId, CommonDataKinds.Note.CONTENT_ITEM_TYPE))
265
+ .withValue(CommonDataKinds.Note.NOTE, note)
266
+ ops.add(op.build())
267
+ op.withYieldAllowed(true)
268
+ if (!TextUtils.isEmpty(photoUri) || !TextUtils.isEmpty(rawPhotoUri)) {
269
+ val photo = getThumbnailBitmap(if (TextUtils.isEmpty(rawPhotoUri)) photoUri else rawPhotoUri)
270
+ ops.add(
271
+ ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
272
+ .withSelection(selection, arrayOf(rawContactId, CommonDataKinds.Photo.CONTENT_ITEM_TYPE))
273
+ .build()
274
+ )
275
+ ops.add(
276
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
277
+ .withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
278
+ .withValue(Columns.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
279
+ .withValue(CommonDataKinds.Photo.PHOTO, toByteArray(photo))
280
+ .build()
281
+ )
282
+ }
283
+ for (map in baseModels) {
284
+ for (item in map) {
285
+ ops.add(item.getDeleteOperation(rawContactId!!))
286
+ ops.add(item.getInsertOperation(rawContactId))
287
+ }
288
+ }
289
+ return ops
290
+ }
291
+
292
+ private val baseModels: Array<List<BaseModel>>
293
+ get() = arrayOf(dates, emails, imAddresses, phones, addresses, relationships, urlAddresses, extraNames)
294
+
295
+ // convert to react native object
296
+ @Throws(ParseException::class)
297
+ fun toMap(fieldSet: Set<String>): Bundle {
298
+ val contact = Bundle().apply {
299
+ putString("lookupKey", lookupKey)
300
+ putString("id", contactId)
301
+ putString("name", if (!displayName.isNullOrEmpty()) displayName else "$firstName $lastName")
302
+
303
+ firstName
304
+ ?.takeIf(String::isNotEmpty)
305
+ ?.let { putString("firstName", it) }
306
+
307
+ middleName
308
+ ?.takeIf(String::isNotEmpty)
309
+ ?.let { putString("middleName", it) }
310
+
311
+ lastName
312
+ ?.takeIf(String::isNotEmpty)
313
+ ?.let { putString("lastName", it) }
314
+
315
+ suffix
316
+ ?.takeIf(String::isNotEmpty)
317
+ ?.let { putString("nameSuffix", it) }
318
+
319
+ prefix
320
+ ?.takeIf(String::isNotEmpty)
321
+ ?.let { putString("namePrefix", it) }
322
+
323
+ phoneticFirstName
324
+ ?.takeIf(String::isNotEmpty)
325
+ ?.let { putString("phoneticFirstName", it) }
326
+
327
+ phoneticLastName
328
+ ?.takeIf(String::isNotEmpty)
329
+ ?.let { putString("phoneticLastName", it) }
330
+
331
+ phoneticMiddleName
332
+ ?.takeIf(String::isNotEmpty)
333
+ ?.let { putString("phoneticMiddleName", it) }
334
+
335
+ putString("contactType", contactType)
336
+
337
+ company
338
+ ?.takeIf(String::isNotEmpty)
339
+ ?.let { putString("company", it) }
340
+
341
+ jobTitle
342
+ ?.takeIf(String::isNotEmpty)
343
+ ?.let { putString("jobTitle", it) }
344
+
345
+ department
346
+ ?.takeIf(String::isNotEmpty)
347
+ ?.let { putString("department", it) }
348
+
349
+ putBoolean("imageAvailable", hasPhoto)
350
+ }
351
+
352
+ if (fieldSet.contains("image") && photoUri != null) {
353
+ contact.putBundle(
354
+ "image",
355
+ Bundle().apply {
356
+ putString("uri", photoUri)
357
+ }
358
+ )
359
+ }
360
+ if (fieldSet.contains("rawImage") && rawPhotoUri != null) {
361
+ contact.putBundle(
362
+ "image",
363
+ Bundle().apply {
364
+ putString("uri", rawPhotoUri)
365
+ }
366
+ )
367
+ }
368
+ if (fieldSet.contains("note") && !TextUtils.isEmpty(note)) {
369
+ contact.putString("note", note)
370
+ }
371
+ if (fieldSet.contains("phoneNumbers") && phones.size > 0) {
372
+ contact.putParcelableArrayList("phoneNumbers", ArrayList(phones.map { it.map }))
373
+ }
374
+ if (fieldSet.contains("emails") && emails.size > 0) {
375
+ contact.putParcelableArrayList("emails", ArrayList(emails.map { it.map }))
376
+ }
377
+ if (fieldSet.contains("addresses") && addresses.size > 0) {
378
+ contact.putParcelableArrayList("addresses", ArrayList(addresses.map { it.map }))
379
+ }
380
+ if (fieldSet.contains("instantMessageAddresses") && imAddresses.size > 0) {
381
+ contact.putParcelableArrayList("instantMessageAddresses", ArrayList(imAddresses.map { it.map }))
382
+ }
383
+ if (fieldSet.contains("urlAddresses") && urlAddresses.size > 0) {
384
+ contact.putParcelableArrayList("urlAddresses", ArrayList(urlAddresses.map { it.map }))
385
+ }
386
+ if (fieldSet.contains("relationships") && relationships.size > 0) {
387
+ contact.putParcelableArrayList("relationships", ArrayList(relationships.map { it.map }))
388
+ }
389
+ if (extraNames.size > 0) {
390
+ val showNickname = fieldSet.contains("nickname")
391
+ val showMaidenName = fieldSet.contains("maidenName")
392
+ for (i in extraNames.indices) {
393
+ val item = extraNames[i]
394
+ val data = item.data
395
+ val label = item.label
396
+ if (showMaidenName && label != null && label == "maidenName" && !TextUtils.isEmpty(data)) {
397
+ contact.putString(label, data)
398
+ }
399
+ if (showNickname && label != null && label == "nickname" && !TextUtils.isEmpty(data)) {
400
+ contact.putString(label, data)
401
+ }
402
+ }
403
+ }
404
+ val showBirthday = fieldSet.contains("birthday")
405
+ val showDates = fieldSet.contains("dates")
406
+ if (showDates || showBirthday) { // double if check with query with cursor
407
+ var hasYear: Boolean
408
+ val rawDatesArray = ArrayList<Bundle?>()
409
+ val datesArray = ArrayList<Bundle?>()
410
+ for (item in dates) {
411
+ val calendar = Calendar.getInstance()
412
+ val datePattern = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
413
+ val noYearPattern = SimpleDateFormat("--MM-dd", Locale.getDefault())
414
+ val details = Bundle()
415
+ val dateString = item.data
416
+ val label = item.label
417
+ val rawDate = Bundle().apply {
418
+ putString("type", label)
419
+ putString("value", dateString)
420
+ }
421
+ rawDatesArray.add(rawDate)
422
+ try {
423
+ hasYear = !dateString!!.startsWith("--")
424
+ if (hasYear) {
425
+ calendar.time = datePattern.parse(dateString)!!
426
+ } else {
427
+ calendar.time = noYearPattern.parse(dateString)!!
428
+ }
429
+ if (hasYear) {
430
+ details.putInt("year", calendar[Calendar.YEAR])
431
+ }
432
+ details.putInt("month", calendar[Calendar.MONTH])
433
+ details.putInt("day", calendar[Calendar.DAY_OF_MONTH])
434
+ // TODO: Evan: The type is only supported in 26+
435
+ details.putString("format", "gregorian")
436
+ if (showBirthday && label != null && label == "birthday") {
437
+ contact.putBundle("birthday", details)
438
+ } else {
439
+ details.putString("label", label)
440
+ datesArray.add(details)
441
+ }
442
+ } catch (e: Exception) {
443
+ Log.w("Contact", e.toString())
444
+ }
445
+ }
446
+ if (showDates) {
447
+ if (datesArray.size > 0) {
448
+ contact.putParcelableArrayList("dates", datesArray)
449
+ }
450
+ }
451
+ if (rawDatesArray.size > 0) {
452
+ contact.putParcelableArrayList("rawDates", rawDatesArray)
453
+ }
454
+ }
455
+ return contact
456
+ }
457
+
458
+ val contentValues: ArrayList<ContentValues>
459
+ get() {
460
+ val contactData = ArrayList<ContentValues>()
461
+ val name = ContentValues().apply {
462
+ put(ContactsContract.Contacts.Data.MIMETYPE, CommonDataKinds.Identity.CONTENT_ITEM_TYPE)
463
+ put(StructuredName.GIVEN_NAME, firstName)
464
+ put(StructuredName.MIDDLE_NAME, middleName)
465
+ put(StructuredName.FAMILY_NAME, lastName)
466
+ put(StructuredName.PREFIX, prefix)
467
+ put(StructuredName.SUFFIX, suffix)
468
+ put(StructuredName.PHONETIC_GIVEN_NAME, phoneticFirstName)
469
+ put(StructuredName.PHONETIC_MIDDLE_NAME, phoneticMiddleName)
470
+ put(StructuredName.PHONETIC_FAMILY_NAME, phoneticLastName)
471
+ }
472
+ contactData.add(name)
473
+
474
+ val organization = ContentValues().apply {
475
+ put(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
476
+ put(CommonDataKinds.Organization.COMPANY, company)
477
+ put(CommonDataKinds.Organization.TITLE, jobTitle)
478
+ put(CommonDataKinds.Organization.DEPARTMENT, department)
479
+ }
480
+ contactData.add(organization)
481
+
482
+ val notes = ContentValues().apply {
483
+ put(ContactsContract.Data.MIMETYPE, CommonDataKinds.Note.CONTENT_ITEM_TYPE)
484
+ put(CommonDataKinds.Note.NOTE, note)
485
+ }
486
+ contactData.add(notes)
487
+
488
+ if (!photoUri.isNullOrBlank()) {
489
+ val photo = getThumbnailBitmap(Uri.parse(photoUri).path)
490
+ val image = ContentValues().apply {
491
+ put(Columns.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
492
+ put(CommonDataKinds.Photo.PHOTO, toByteArray(photo))
493
+ }
494
+ contactData.add(image)
495
+ }
496
+ if (!rawPhotoUri.isNullOrBlank()) {
497
+ val photo = getThumbnailBitmap(rawPhotoUri)
498
+ val image = ContentValues().apply {
499
+ put(Columns.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
500
+ put(CommonDataKinds.Photo.PHOTO, toByteArray(photo))
501
+ }
502
+ contactData.add(image)
503
+ }
504
+ for (map in baseModels) {
505
+ for (item in map) {
506
+ contactData.add(item.contentValues)
507
+ }
508
+ }
509
+ return contactData
510
+ }
511
+
512
+ private fun getThumbnailBitmap(photoUri: String?): Bitmap {
513
+ val path = Uri.parse(photoUri).path
514
+ return BitmapFactory.decodeFile(path)
515
+ }
516
+ }