Skip to content

Feature/add support for custom fields #139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 1 addition & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ buildscript {
ext.kotlin_coroutines_version = '1.6.4'
repositories {
google()
jcenter()
mavenCentral()
}

Expand All @@ -19,7 +18,6 @@ buildscript {
rootProject.allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
Expand Down Expand Up @@ -51,4 +49,4 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
}
}
14 changes: 11 additions & 3 deletions android/src/main/kotlin/co/quis/flutter_contacts/Contact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package co.quis.flutter_contacts

import co.quis.flutter_contacts.properties.Account
import co.quis.flutter_contacts.properties.Address
import co.quis.flutter_contacts.properties.CustomField
import co.quis.flutter_contacts.properties.Email
import co.quis.flutter_contacts.properties.Event
import co.quis.flutter_contacts.properties.Group
import co.quis.flutter_contacts.properties.Name
import co.quis.flutter_contacts.properties.Note
import co.quis.flutter_contacts.properties.Organization
import co.quis.flutter_contacts.properties.Phone
import co.quis.flutter_contacts.properties.Relation
import co.quis.flutter_contacts.properties.SocialMedia
import co.quis.flutter_contacts.properties.Website

Expand All @@ -26,9 +28,11 @@ data class Contact(
var websites: List<Website> = listOf(),
var socialMedias: List<SocialMedia> = listOf(),
var events: List<Event> = listOf(),
var relations: List<Relation> = listOf(),
var notes: List<Note> = listOf(),
var accounts: List<Account> = listOf(),
var groups: List<Group> = listOf()
var groups: List<Group> = listOf(),
var customFields: List<CustomField> = listOf()
) {
companion object {
fun fromMap(m: Map<String, Any?>): Contact {
Expand All @@ -46,9 +50,11 @@ data class Contact(
(m["websites"] as List<Map<String, Any>>).map { Website.fromMap(it) },
(m["socialMedias"] as List<Map<String, Any>>).map { SocialMedia.fromMap(it) },
(m["events"] as List<Map<String, Any?>>).map { Event.fromMap(it) },
(m["relations"] as List<Map<String, Any>>).map { Relation.fromMap(it) },
(m["notes"] as List<Map<String, Any>>).map { Note.fromMap(it) },
(m["accounts"] as List<Map<String, Any>>).map { Account.fromMap(it) },
(m["groups"] as List<Map<String, Any>>).map { Group.fromMap(it) }
(m["groups"] as List<Map<String, Any>>).map { Group.fromMap(it) },
(m["customFields"] as List<Map<String, Any>>).map { CustomField.fromMap(it) }
)
}
}
Expand All @@ -67,8 +73,10 @@ data class Contact(
"websites" to websites.map { it.toMap() },
"socialMedias" to socialMedias.map { it.toMap() },
"events" to events.map { it.toMap() },
"relations" to relations.map { it.toMap() },
"notes" to notes.map { it.toMap() },
"accounts" to accounts.map { it.toMap() },
"groups" to groups.map { it.toMap() }
"groups" to groups.map { it.toMap() },
"customFields" to customFields.map { it.toMap() }
)
}
103 changes: 100 additions & 3 deletions android/src/main/kotlin/co/quis/flutter_contacts/FlutterContacts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,29 @@ import android.provider.ContactsContract.CommonDataKinds.Note
import android.provider.ContactsContract.CommonDataKinds.Organization
import android.provider.ContactsContract.CommonDataKinds.Phone
import android.provider.ContactsContract.CommonDataKinds.Photo
import android.provider.ContactsContract.CommonDataKinds.Relation
import android.provider.ContactsContract.CommonDataKinds.StructuredName
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal
import android.provider.ContactsContract.CommonDataKinds.Website
import android.provider.ContactsContract.Contacts
import android.provider.ContactsContract.Data
import android.provider.ContactsContract.Groups
import android.provider.ContactsContract.RawContacts
import android.provider.ContactsContract.RawContactsEntity
import java.io.FileNotFoundException
import java.io.InputStream
import java.io.OutputStream
import co.quis.flutter_contacts.properties.Account as PAccount
import co.quis.flutter_contacts.properties.Address as PAddress
import co.quis.flutter_contacts.properties.CustomField as PCustomField
import co.quis.flutter_contacts.properties.Email as PEmail
import co.quis.flutter_contacts.properties.Event as PEvent
import co.quis.flutter_contacts.properties.Group as PGroup
import co.quis.flutter_contacts.properties.Name as PName
import co.quis.flutter_contacts.properties.Note as PNote
import co.quis.flutter_contacts.properties.Organization as POrganization
import co.quis.flutter_contacts.properties.Phone as PPhone
import co.quis.flutter_contacts.properties.Relation as PRelation
import co.quis.flutter_contacts.properties.SocialMedia as PSocialMedia
import co.quis.flutter_contacts.properties.Website as PWebsite

Expand All @@ -52,6 +56,8 @@ class FlutterContacts {
val REQUEST_CODE_PICK = 77883
val REQUEST_CODE_INSERT = 77884

private const val CUSTOM_FIELD_MIMETYPE = "vnd.com.google.cursor.item/contact_user_defined_field";

fun select(
resolver: ContentResolver,
id: String?,
Expand Down Expand Up @@ -127,7 +133,11 @@ class FlutterContacts {
Event.START_DATE,
Event.TYPE,
Event.LABEL,
Note.NOTE
Relation.TYPE,
Relation.LABEL,
Note.NOTE,
RawContactsEntity.DATA1,
RawContactsEntity.DATA2,
)
)
}
Expand Down Expand Up @@ -192,6 +202,7 @@ class FlutterContacts {
fun getBool(col: String): Boolean = getInt(col) == 1

while (cursor.moveToNext()) {

// ID and display name.
val id = if (returnUnifiedContacts) getString(Data.CONTACT_ID) else getString(Data.RAW_CONTACT_ID)
if (id !in index) {
Expand Down Expand Up @@ -382,6 +393,17 @@ class FlutterContacts {
contact.events += event
}
}
Relation.CONTENT_ITEM_TYPE -> {
val label: String = getRelationLabel(cursor)
val customLabel: String =
if (label == "custom") getRelationCustomLabel(cursor) else ""
val relation = PRelation(
getString(Relation.NAME),
label,
customLabel
)
contact.relations += relation
}
Note.CONTENT_ITEM_TYPE -> {
val note: String = getString(Note.NOTE)
// It seems that every contact has an empty note by default;
Expand All @@ -399,6 +421,11 @@ class FlutterContacts {
}
}
}
CUSTOM_FIELD_MIMETYPE -> {
contact.customFields += PCustomField(
getString(RawContactsEntity.DATA1),
getString(RawContactsEntity.DATA2))
}
}
}
}
Expand Down Expand Up @@ -504,7 +531,7 @@ class FlutterContacts {
ops.add(
ContentProviderOperation.newDelete(Data.CONTENT_URI)
.withSelection(
"${RawContacts.CONTACT_ID}=? and ${Data.MIMETYPE} in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
"${RawContacts.CONTACT_ID}=? and ${Data.MIMETYPE} in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
arrayOf(
contactId,
StructuredName.CONTENT_ITEM_TYPE,
Expand All @@ -516,7 +543,9 @@ class FlutterContacts {
Website.CONTENT_ITEM_TYPE,
Im.CONTENT_ITEM_TYPE,
Event.CONTENT_ITEM_TYPE,
Note.CONTENT_ITEM_TYPE
Relation.CONTENT_ITEM_TYPE,
Note.CONTENT_ITEM_TYPE,
CUSTOM_FIELD_MIMETYPE,
)
)
.build()
Expand Down Expand Up @@ -996,6 +1025,54 @@ class FlutterContacts {
}
}

private fun getRelationLabel(cursor: Cursor): String {
val type = cursor.getInt(cursor.getColumnIndex(Relation.TYPE))
return when (type) {
Relation.TYPE_CUSTOM -> "custom"
Relation.TYPE_ASSISTANT -> "assistant"
Relation.TYPE_BROTHER -> "brother"
Relation.TYPE_CHILD -> "child"
Relation.TYPE_DOMESTIC_PARTNER -> "domestic-partner"
Relation.TYPE_FATHER -> "father"
Relation.TYPE_FRIEND -> "friend"
Relation.TYPE_MANAGER -> "manager"
Relation.TYPE_MOTHER -> "mother"
Relation.TYPE_PARENT -> "parent"
Relation.TYPE_PARTNER -> "partner"
Relation.TYPE_REFERRED_BY -> "referred-by"
Relation.TYPE_RELATIVE -> "relative"
Relation.TYPE_SISTER -> "sister"
Relation.TYPE_SPOUSE -> "spouse"
else -> "relation"
}
}

private fun getRelationCustomLabel(cursor: Cursor): String {
return cursor.getString(cursor.getColumnIndex(Relation.LABEL)) ?: ""
}

private data class RelationLabelPair(val label: Int, val customLabel: String)
private fun getRelationLabelInv(label: String, customLabel: String): RelationLabelPair {
return when (label) {
"custom" -> RelationLabelPair(Relation.TYPE_CUSTOM, customLabel)
"assistant" -> RelationLabelPair(Relation.TYPE_ASSISTANT, "")
"brother" -> RelationLabelPair(Relation.TYPE_BROTHER, "")
"child" -> RelationLabelPair(Relation.TYPE_CHILD, "")
"domestic-partner" -> RelationLabelPair(Relation.TYPE_DOMESTIC_PARTNER, "")
"father" -> RelationLabelPair(Relation.TYPE_FATHER, "")
"friend" -> RelationLabelPair(Relation.TYPE_FRIEND, "")
"manager" -> RelationLabelPair(Relation.TYPE_MANAGER, "")
"mother" -> RelationLabelPair(Relation.TYPE_MOTHER, "")
"parent" -> RelationLabelPair(Relation.TYPE_PARENT, "")
"partner" -> RelationLabelPair(Relation.TYPE_PARTNER, "")
"referred-by" -> RelationLabelPair(Relation.TYPE_REFERRED_BY, "")
"relative" -> RelationLabelPair(Relation.TYPE_RELATIVE, "")
"sister" -> RelationLabelPair(Relation.TYPE_SISTER, "")
"spouse" -> RelationLabelPair(Relation.TYPE_SPOUSE, "")
else -> RelationLabelPair(Email.TYPE_CUSTOM, label)
}
}

private fun buildOpsForContact(
contact: Contact,
ops: MutableList<ContentProviderOperation>,
Expand Down Expand Up @@ -1131,6 +1208,26 @@ class FlutterContacts {
.build()
)
}
for ((i, relation) in contact.relations.withIndex()) {
val labelPair: RelationLabelPair = getRelationLabelInv(relation.label, relation.customLabel)
ops.add(
newInsert()
.withValue(Data.MIMETYPE, Relation.CONTENT_ITEM_TYPE)
.withValue(Relation.NAME, emptyToNull(relation.name))
.withValue(Relation.TYPE, labelPair.label)
.withValue(Relation.LABEL, emptyToNull(labelPair.customLabel))
.build()
)
}
for ((i, customField) in contact.customFields.withIndex()) {
ops.add(
newInsert()
.withValue(Data.MIMETYPE, CUSTOM_FIELD_MIMETYPE)
.withValue(RawContactsEntity.DATA1, emptyToNull(customField.name))
.withValue(RawContactsEntity.DATA2, emptyToNull(customField.label))
.build()
)
}
for (note in contact.notes) {
if (!note.note.isEmpty()) {
ops.add(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package co.quis.flutter_contacts.properties


data class CustomField(
var name: String,
var label: String,
) {
companion object {
fun fromMap(m: Map<String, Any>): CustomField = CustomField(
m["name"] as String,
m["label"] as String,
)
}

fun toMap(): Map<String, Any> = mapOf(
"name" to name,
"label" to label,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package co.quis.flutter_contacts.properties

data class Relation(
var name: String,
// one of: assistant, brother, child, daughter, domestic-partner, father, friend, manager,
// mother, other, parent, partner, referred-by, relative, sister, son, spouse, custom
var label: String = "relative",
var customLabel: String = ""
) {
companion object {
fun fromMap(m: Map<String, Any>): Relation = Relation(
m["name"] as String,
m["label"] as String,
m["customLabel"] as String
)
}

fun toMap(): Map<String, Any> = mapOf(
"name" to name,
"label" to label,
"customLabel" to customLabel
)
}
2 changes: 1 addition & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "co.quis.flutter_contacts_example"
minSdkVersion 16
minSdkVersion flutter.minSdkVersion
targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
6 changes: 3 additions & 3 deletions example_full/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 31
compileSdkVersion 33

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand All @@ -39,8 +39,8 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "co.quis.flutter_contacts_example"
minSdkVersion 16
targetSdkVersion 31
minSdkVersion flutter.minSdkVersion
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand Down
6 changes: 3 additions & 3 deletions example_full/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ buildscript {
ext.kotlin_version = '1.7.21'
repositories {
google()
jcenter()
mavenCentral()
}

dependencies {
Expand All @@ -14,7 +14,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

Expand All @@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
Loading