Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e58e03c
adjust cursor position on skipping last record
deepak786 Jul 13, 2021
516ae51
update comment
deepak786 Jul 13, 2021
337df15
android: openContactPicker, insertOrUpdateContactViaPicker
deepak786 Jul 14, 2021
a1dfff8
flutter: openContactPicker, insertOrUpdateContactViaPicker
deepak786 Jul 14, 2021
6ff87e0
openContactPicker iOS
deepak786 Jul 14, 2021
fa3d3e1
updated key from successful to success as succes is returned from and…
deepak786 Jul 14, 2021
70bdf88
close the controller on cancel the stream
deepak786 Jul 15, 2021
411e13b
insertOrUpdateContactViaPicker ios
deepak786 Jul 15, 2021
732f45d
adjusted CNContactViewController to have swipe down
deepak786 Jul 16, 2021
d5b4081
commented displayedPropertyKeys
deepak786 Jul 16, 2021
715c3d7
return contact with insertOrUpdateContactViaPicker
deepak786 Jul 21, 2021
21b44ed
Merge branch 'SunnyApp:master' into agendaboa_patch
deepak786 Aug 1, 2021
71b4a57
migrate to v2 embedding
deepak786 Sep 28, 2021
8436e8b
Merge branch 'SunnyApp:master' into agendaboa_patch
deepak786 Nov 9, 2021
a0192a2
get primary display name for unified mode
deepak786 Dec 22, 2021
9a48e15
set the display name outside the mime type check and set to primary a…
deepak786 Dec 22, 2021
78c09c3
add non null operator (!!)
deepak786 May 12, 2022
c0bc69b
Override flexidate from https://github.com/agendaboa/flexidate-0.8.0-3
deepak786 Nov 18, 2022
7724b41
Use flexidate from https://github.com/agendaboa/flexidate-0.8.0-3
rohansohonee Nov 18, 2022
571a4bc
Upgraded kotlin_version as required for Flutter 2.10.x (#1)
tkeithblack Nov 18, 2022
8ddc09f
Catch the MethodCallException's
rohansohonee1 Feb 17, 2023
ff27522
Use default value "0"
rohansohonee1 Sep 29, 2023
99d8c52
Update iOS version to 11
rohansohonee1 Sep 29, 2023
7baf4d0
Add `PERMISSION_CONTACTS`
rohansohonee1 Sep 29, 2023
3c8a72a
Update `uuid` dependency
rohansohonee1 Nov 24, 2023
914ea20
Add `namespace`
rohansohonee1 Nov 24, 2023
0bf0ea6
Add compile & kotlin options
rohansohonee1 Nov 24, 2023
20b8ed6
Remove V1
rohansohonee1 May 21, 2025
07bd6ce
Remove null
rohansohonee1 May 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
@file:Suppress("NAME_SHADOWING")

package co.sunnyapp.flutter_contact

import android.content.ContentValues
import android.content.Intent
import android.provider.ContactsContract
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry

/**
* Base class that facilitates edit/add contacts.
*
* There will be two modes.
* for embedding v1 - FlutterContactFormsPluginOld which handles the starting of the intent with registrar.
* for embedding v2 - FlutterContactFormsPlugin which handles the starting of the intent with ActivityPluginBinding.
*/
abstract class BaseFlutterContactForms(private val plugin: BaseFlutterContactPlugin) : PluginRegistry.ActivityResultListener {
companion object {
const val REQUEST_OPEN_CONTACT_FORM = 52941
const val REQUEST_OPEN_EXISTING_CONTACT = 52942
const val REQUEST_OPEN_CONTACT_PICKER = 52943
const val REQUEST_INSERT_OR_UPDATE_CONTACT = 52944
}

var result: MethodChannel.Result? = null
private set

abstract fun startIntent(intent: Intent, request: Int)

override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?): Boolean {
val success = when (requestCode) {
REQUEST_OPEN_EXISTING_CONTACT, REQUEST_OPEN_CONTACT_FORM, REQUEST_INSERT_OR_UPDATE_CONTACT -> when (val uri = intent?.data) {
null -> {
result?.success(mapOf("success" to false, "code" to ErrorCodes.FORM_OPERATION_CANCELED))
false
}
else -> when (val contactIdString = uri.lastPathSegment) {
null -> {
result?.success(mapOf("success" to false, "code" to ErrorCodes.FORM_OPERATION_CANCELED))
false
}
else -> {
result?.success(mapOf(
"success" to true,
"contact" to plugin.getContact(ContactKeys(plugin.mode, contactIdString.toLong()),
withThumbnails = false, photoHighResolution = false)))
true
}
}
}
REQUEST_OPEN_CONTACT_PICKER -> when (val uri = intent?.data) {
null -> {
result?.success(mapOf("success" to false, "code" to ErrorCodes.PICKER_OPERATION_CANCELED))
false
}
else -> when (val contactIdString = uri.lastPathSegment) {
null -> {
result?.success(mapOf("success" to false, "code" to ErrorCodes.PICKER_OPERATION_CANCELED))
false
}
else -> {
result?.success(mapOf(
"success" to true,
"contact" to plugin.getContact(ContactKeys(plugin.mode, contactIdString.toLong()),
withThumbnails = false, photoHighResolution = false)))
true
}
}
}
else -> {
result?.success(ErrorCodes.FORM_COULD_NOT_BE_OPENED)
false
}
}
result = null
return success
}

fun openContactEditForm(result: MethodChannel.Result, contactId: ContactKeys) {
this.result = result
try {
val lookupUri = contactId.mode.lookupUri(plugin.resolver, contactId)
// val contact = plugin.getContactRecord(contactId, withThumbnails = false, photoHighResolution = false)
val intent = Intent(Intent.ACTION_EDIT)
intent.setDataAndTypeAndNormalize(lookupUri, ContactsContract.Contacts.CONTENT_ITEM_TYPE)
intent.putExtra("finishActivityOnSaveCompleted", true)
startIntent(intent, REQUEST_OPEN_EXISTING_CONTACT)
} catch (e: MethodCallException) {
result.error(e.code, "Error with ${e.method}: ${e.error}", e.error)
this.result = null
} catch (e: Exception) {
result.error(ErrorCodes.UNKNOWN_ERROR, "Unable to open form", e.toString())
this.result = null
}
}

fun openContactInsertForm(result: MethodChannel.Result, mode: ContactMode, contact: Contact) {
try {
this.result = result
val intent = Intent(Intent.ACTION_INSERT, mode.contentUri)
contact.applyToIntent(mode, intent)
intent.putExtra("finishActivityOnSaveCompleted", true)
startIntent(intent, REQUEST_OPEN_CONTACT_FORM)
} catch (e: MethodCallException) {
result.error(e.code, "Error with ${e.method}: ${e.error}", e.error)
this.result = null
} catch (e: Exception) {
result.error(ErrorCodes.UNKNOWN_ERROR, "Problem opening contact form", "$e")
this.result = null
}
}

fun openContactPicker(result: MethodChannel.Result, mode: ContactMode) {
try {
this.result = result
val intent = Intent(Intent.ACTION_PICK)
intent.type = ContactsContract.Contacts.CONTENT_TYPE
startIntent(intent, REQUEST_OPEN_CONTACT_PICKER)
} catch (e: MethodCallException) {
result.error(e.code, "Error with ${e.method}: ${e.error}", e.error)
this.result = null
} catch (e: Exception) {
result.error(ErrorCodes.UNKNOWN_ERROR, "Problem opening contact picker", "$e")
this.result = null
}
}

fun insertOrUpdateContactViaPicker(result: MethodChannel.Result, mode: ContactMode, contact: Contact) {
try {
this.result = result
val intentInsertEdit = Intent(Intent.ACTION_INSERT_OR_EDIT)
intentInsertEdit.type = ContactsContract.Contacts.CONTENT_ITEM_TYPE
contact.applyToIntent(mode, intentInsertEdit)
intentInsertEdit.putExtra("finishActivityOnSaveCompleted", true)
startIntent(intentInsertEdit, REQUEST_INSERT_OR_UPDATE_CONTACT)
} catch (e: MethodCallException) {
result.error(e.code, "Error with ${e.method}: ${e.error}", e.error)
this.result = null
} catch (e: Exception) {
result.error(ErrorCodes.UNKNOWN_ERROR, "Problem opening contact picker", "$e")
this.result = null
}
}
}

/// For now, only copies basic properties
fun Contact.applyToIntent(mode: ContactMode, intent: Intent) {
val contact = this

val inboundData = ArrayList<ContentValues>()

inboundData += contentValues(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.givenName)
.withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName)
.withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.familyName)
.withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, contact.prefix)
.withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, contact.suffix)


inboundData += contentValues(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Note.NOTE, contact.note)


inboundData += contentValues(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, contact.company)
.withValue(ContactsContract.CommonDataKinds.Organization.TITLE, contact.jobTitle)

//Phones
for (phone in contact.phones) {
inboundData += contentValues(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone.value)
.withTypeAndLabel(ItemType.phone, phone.label)
}

//Emails
for (email in contact.emails) {
inboundData += contentValues(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email.value)
.withTypeAndLabel(ItemType.email, email.label)
}

//Postal addresses
for (address in contact.postalAddresses) {
inboundData += contentValues(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)
.withTypeAndLabel(ItemType.address, address.label)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.STREET, address.street)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, address.city)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.REGION, address.region)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, address.postcode)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY, address.country)
}


for (date in contact.dates) {
inboundData += contentValues(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)
.withTypeAndLabel(ItemType.event, date.label)
.withValue(ContactsContract.CommonDataKinds.Event.START_DATE, date.toDateValue())

}

for (url in contact.urls) {
inboundData += contentValues(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE)
.withTypeAndLabel(ItemType.url, url.label)
.withValue(ContactsContract.CommonDataKinds.Website.URL, url.value)
}



intent.apply {
putExtra(ContactsContract.Intents.Insert.EMAIL, emails.firstOrNull()?.value)
putExtra(ContactsContract.Intents.Insert.PHONE, phones.firstOrNull()?.value)
putExtra(ContactsContract.Intents.Insert.NAME, listOfNotNull(contact.givenName, contact.familyName).let {
when {
it.isNotEmpty() -> it
else -> listOfNotNull(contact.displayName)
}
}.joinToString(" "))
putExtra(ContactsContract.Intents.Insert.COMPANY, company)
putExtra(ContactsContract.Intents.Insert.NOTES, note)
putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, inboundData)
}

}

fun ContentValues.withValue(key: String, value: String?) = apply {
val value = value ?: return@apply
put(key, value)
}

fun ContentValues.withValue(key: String, value: Int?) = apply {
val value = value ?: return@apply
put(key, value)
}

fun ContentValues.withTypeAndLabel(type: ItemType, labelString: String?) = apply {
val label = labelString ?: return@apply
return when (val typeInt = type.calculateTypeInt(label)) {
type.otherType -> withValue(type.typeField, typeInt)
.withValue(type.labelField, label)
else -> withValue(type.typeField, typeInt)
}

}

fun contentValues(mimeType: String, vararg values: Pair<String, String?>) = ContentValues().apply {
put(ContactsContract.Data.MIMETYPE, mimeType)
for ((k, v) in values) {
if (v != null) {
put(k, v)
}
}
}
Loading