Skip to content

Commit

Permalink
Add support for Relation
Browse files Browse the repository at this point in the history
  • Loading branch information
alexstyl committed Dec 31, 2021
1 parent 3d75353 commit 0306104
Show file tree
Hide file tree
Showing 16 changed files with 253 additions and 5 deletions.
Expand Up @@ -8,7 +8,9 @@ import com.alexstyl.contactstore.ContactColumn.Note
import com.alexstyl.contactstore.ContactColumn.Organization
import com.alexstyl.contactstore.ContactColumn.Phones
import com.alexstyl.contactstore.ContactColumn.PostalAddresses
import com.alexstyl.contactstore.ContactColumn.Relations
import com.alexstyl.contactstore.ContactColumn.WebAddresses
import com.alexstyl.contactstore.Label.RelationManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.junit.Test
Expand Down Expand Up @@ -211,4 +213,26 @@ class AddValuesToExistingContactContactStoreTest : ContactStoreTestBase() {

assertContactUpdatedNoId(expected)
}

@Test
fun updatesRelation(): Unit = runBlocking {
val contact = buildStoreContact(Names, Relations) {
firstName = "Paolo"
lastName = "Melendez"
}

val expected = contact.mutableCopy().apply {
relations.add(
LabeledValue(
Relation(name = "Maria"),
RelationManager
)
)
}
store.execute {
update(expected)
}

assertContactUpdatedNoId(expected)
}
}
Expand Up @@ -8,6 +8,7 @@ import com.alexstyl.contactstore.ContactColumn.Note
import com.alexstyl.contactstore.ContactColumn.Organization
import com.alexstyl.contactstore.ContactColumn.Phones
import com.alexstyl.contactstore.ContactColumn.PostalAddresses
import com.alexstyl.contactstore.ContactColumn.Relations
import com.alexstyl.contactstore.ContactColumn.WebAddresses
import com.alexstyl.contactstore.Label.DateBirthday
import com.alexstyl.contactstore.Label.LocationHome
Expand Down Expand Up @@ -287,4 +288,23 @@ class ContactStoreInsertContactTest : ContactStoreTestBase() {

assertOnlyContact(actual = actual, expected = expected)
}

@Test
fun insertsContactWithRelation(): Unit = runBlocking {
store.execute {
insert {
relation(name = "Person", label = Label.PhoneNumberAssistant)
}
}

val actual = store.fetchContacts(columnsToFetch = listOf(Relations)).first()
val expected = contact(
relations = listOf(
LabeledValue(Relation(name = "Person"), Label.PhoneNumberAssistant)
),
columns = listOf(Relations)
)

assertOnlyContact(actual = actual, expected = expected)
}
}
Expand Up @@ -70,6 +70,7 @@ abstract class ContactStoreTestBase {
events: List<LabeledValue<EventDate>> = emptyList(),
webAddresses: List<LabeledValue<WebAddress>> = emptyList(),
imAddresses: List<LabeledValue<ImAddress>> = emptyList(),
relations : List<LabeledValue<Relation>> = emptyList(),
note: Note? = null,
prefix: String? = null,
middleName: String? = null,
Expand All @@ -94,7 +95,8 @@ abstract class ContactStoreTestBase {
imAddresses = imAddresses,
phones = phones,
mails = mails,
isStarred = false
isStarred = false,
relations = relations
)
}

Expand Down
Expand Up @@ -10,6 +10,7 @@ import com.alexstyl.contactstore.ContactColumn.Note
import com.alexstyl.contactstore.ContactColumn.Organization
import com.alexstyl.contactstore.ContactColumn.Phones
import com.alexstyl.contactstore.ContactColumn.PostalAddresses
import com.alexstyl.contactstore.ContactColumn.Relations
import com.alexstyl.contactstore.ContactColumn.WebAddresses
import org.hamcrest.Description
import org.hamcrest.Matcher
Expand Down Expand Up @@ -62,6 +63,10 @@ class EqualContentsContactMatcher(
putCommaIfNeeded()
append("nickname = $nickname")
}
if (containsColumn(Relations)) {
putCommaIfNeeded()
append("relations = ${labeledValues(relations)}")
}
if (containsColumn(WebAddresses)) {
putCommaIfNeeded()
append("webAddresses = ${labeledValues(webAddresses)}")
Expand Down Expand Up @@ -145,6 +150,10 @@ class EqualContentsContactMatcher(
mismatchDescription.appendText("note was ${actual.note}")
false
}
relationsAreDifferent(actual) ->{
mismatchDescription.appendText("relations were ${actual.relations}")
false
}
displayName != expected.displayName -> {
mismatchDescription.appendText("display name was '${actual.displayName}'")
false
Expand All @@ -154,6 +163,13 @@ class EqualContentsContactMatcher(
}
}

private fun relationsAreDifferent(actual: Contact): Boolean {
if(expected.containsColumn(Relations).not()) {
return false
}
return areLabeledValuesDifferentIgnoringId(actual.relations, expected.relations)
}

private fun imAreDifferent(actual: Contact): Boolean {
if (expected.containsColumn(ImAddresses).not()) {
return false
Expand Down
7 changes: 7 additions & 0 deletions library/src/main/java/com/alexstyl/contactstore/Contact.kt
Expand Up @@ -21,6 +21,7 @@ import com.alexstyl.contactstore.ContactColumn.Note
import com.alexstyl.contactstore.ContactColumn.Organization
import com.alexstyl.contactstore.ContactColumn.Phones
import com.alexstyl.contactstore.ContactColumn.PostalAddresses
import com.alexstyl.contactstore.ContactColumn.Relations
import com.alexstyl.contactstore.ContactColumn.WebAddresses

interface Contact {
Expand Down Expand Up @@ -146,6 +147,11 @@ interface Contact {
*/
val organization: String?

/**
* Requires: [ContactColumn.Relations]
*/
val relations: List<LabeledValue<Relation>>

/**
* Requires: [ContactColumn.Organization]
*/
Expand Down Expand Up @@ -189,6 +195,7 @@ fun Contact.mutableCopy(): MutableContact {
else
mutableListOf(),
imAddresses = if (containsColumn(ImAddresses)) imAddresses.toMutableList() else mutableListOf(),
relations = if(containsColumn(Relations)) relations.toMutableList() else mutableListOf(),
note = if (containsColumn(Note)) note else null,
columns = columns,
middleName = if (containsColumn(Names)) middleName else null,
Expand Down
Expand Up @@ -71,6 +71,11 @@ sealed class ContactColumn {
* A column that will populate the [Contact.imAddresses] field of all queried contacts when requested.
*/
object ImAddresses : ContactColumn()

/**
* A column that will populate the [Contact.relations] field of all queried contacts when requested.
*/
object Relations : ContactColumn()
}

fun standardColumns(): List<ContactColumn> {
Expand All @@ -87,5 +92,6 @@ fun standardColumns(): List<ContactColumn> {
ContactColumn.Organization,
ContactColumn.GroupMemberships,
ContactColumn.ImAddresses,
ContactColumn.Relations,
)
}
@@ -1,6 +1,7 @@
package com.alexstyl.contactstore

import com.alexstyl.contactstore.ContactColumn.Events
import com.alexstyl.contactstore.ContactColumn.ImAddresses
import com.alexstyl.contactstore.ContactColumn.Image
import com.alexstyl.contactstore.ContactColumn.Mails
import com.alexstyl.contactstore.ContactColumn.Names
Expand Down
33 changes: 33 additions & 0 deletions library/src/main/java/com/alexstyl/contactstore/ContactQueries.kt
Expand Up @@ -9,6 +9,7 @@ import android.provider.ContactsContract.CommonDataKinds.Note as NoteColumns
import android.provider.ContactsContract.CommonDataKinds.Organization as OrganizationColumns
import android.provider.ContactsContract.CommonDataKinds.Phone as PhoneColumns
import android.provider.ContactsContract.CommonDataKinds.Photo as PhotoColumns
import android.provider.ContactsContract.CommonDataKinds.Relation as RelationColumns
import android.provider.ContactsContract.CommonDataKinds.StructuredName as NameColumns
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal as PostalColumns
import android.provider.ContactsContract.CommonDataKinds.Website as WebColumns
Expand Down Expand Up @@ -229,6 +230,7 @@ internal class ContactQueries(
val webAddresses = mutableSetOf<LabeledValue<WebAddress>>()
val events = mutableSetOf<LabeledValue<EventDate>>()
val imAddresses = mutableSetOf<LabeledValue<ImAddress>>()
val relations = mutableSetOf<LabeledValue<Relation>>()
val postalAddresses = mutableSetOf<LabeledValue<PostalAddress>>()
var organization: String? = null
var jobTitle: String? = null
Expand Down Expand Up @@ -367,6 +369,14 @@ internal class ContactQueries(
)
}
}
RelationColumns.CONTENT_ITEM_TYPE -> {
val name = row[RelationColumns.NAME]
val id = row[RelationColumns._ID].toLongOrNull()
if (name.isNotBlank() && id != null) {
val label = relationLabel(row)
relations.add(LabeledValue(Relation(name), label, id))
}
}
else -> {
val mimeType = linkedAccountMimeTypes[mimetype]
if (mimeType != null) {
Expand Down Expand Up @@ -415,10 +425,32 @@ internal class ContactQueries(
groups = groupIds.toList(),
linkedAccountValues = linkedAccountValues.toList(),
imAddresses = imAddresses.toList(),
relations = relations.toList()
)
}
}

private fun relationLabel(row: Cursor): Label {
return when (row[RelationColumns.TYPE].toIntOrNull()) {
RelationColumns.TYPE_ASSISTANT -> Label.PhoneNumberAssistant
RelationColumns.TYPE_BROTHER -> Label.RelationBrother
RelationColumns.TYPE_CHILD -> Label.RelationChild
RelationColumns.TYPE_DOMESTIC_PARTNER -> Label.RelationDomesticPartner
RelationColumns.TYPE_FATHER -> Label.RelationFather
RelationColumns.TYPE_FRIEND -> Label.RelationFriend
RelationColumns.TYPE_MANAGER -> Label.RelationManager
RelationColumns.TYPE_MOTHER -> Label.RelationMother
RelationColumns.TYPE_PARENT -> Label.RelationParent
RelationColumns.TYPE_PARTNER -> Label.RelationPartner
RelationColumns.TYPE_REFERRED_BY -> Label.RelationReferredBy
RelationColumns.TYPE_RELATIVE -> Label.RelationRelative
RelationColumns.TYPE_SISTER -> Label.RelationSister
RelationColumns.TYPE_SPOUSE -> Label.RelationSpouse
RelationColumns.TYPE_CUSTOM -> Label.Custom(row[RelationColumns.LABEL])
else -> Label.Other
}
}

private fun getImProtocol(fromCursor: Cursor): String {
// starting from Android 31, type will always be PROTOCOL_CUSTOM according to docs
// the else covers legacy versions
Expand Down Expand Up @@ -458,6 +490,7 @@ internal class ContactQueries(
Nickname -> NicknameColumns.CONTENT_ITEM_TYPE
GroupMemberships -> GroupColumns.CONTENT_ITEM_TYPE
ImAddresses -> ImColumns.CONTENT_ITEM_TYPE
Relations -> RelationColumns.CONTENT_ITEM_TYPE
is LinkedAccountValues ->
error("Tried to map a LinkedAccountColumn as standard column")
}
Expand Down
Expand Up @@ -33,6 +33,7 @@ fun SaveRequest.insert(builder: MutableContactBuilder.() -> Unit) {
postalAddresses.addAll(values.postalAddresses)
webAddresses.addAll(values.webAddresses)
groups.addAll(values.groupMemberships)
relations.addAll(values.relations)
imAddresses.addAll(values.imAddresses)
})
}
Expand Up @@ -65,4 +65,6 @@ internal fun GroupMembership.requireId(): Long {
return requireNotNull(_id)
}

data class LookupKey(val value: String)
data class LookupKey(val value: String)

data class Relation(val name: String)
Expand Up @@ -11,6 +11,7 @@ import android.provider.ContactsContract.CommonDataKinds.Photo as PhotoColumns
import android.provider.ContactsContract.CommonDataKinds.StructuredName as NameColumns
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal as PostalColumns
import android.provider.ContactsContract.CommonDataKinds.Website as WebAddressColumns
import android.provider.ContactsContract.CommonDataKinds.Relation as RelationColumns
import android.content.ContentProviderOperation
import android.content.ContentProviderOperation.newDelete
import android.content.ContentProviderOperation.newInsert
Expand All @@ -31,6 +32,7 @@ import com.alexstyl.contactstore.ContactColumn.Note
import com.alexstyl.contactstore.ContactColumn.Organization
import com.alexstyl.contactstore.ContactColumn.Phones
import com.alexstyl.contactstore.ContactColumn.PostalAddresses
import com.alexstyl.contactstore.ContactColumn.Relations
import com.alexstyl.contactstore.ContactColumn.WebAddresses
import com.alexstyl.contactstore.utils.get
import com.alexstyl.contactstore.utils.runQuery
Expand Down Expand Up @@ -62,6 +64,7 @@ internal class ExistingContactOperationsFactory(
updateGroupMembership(newContact = contact, oldContact = existingContact) +
updatePostalAddresses(newContact = contact, oldContact = existingContact) +
updateImAddresses(newContact = contact, oldContact = existingContact) +
updateRelations(newContact = contact, oldContact = existingContact) +
replaceWebAddresses(newContact = contact, oldContact = existingContact)
}

Expand Down Expand Up @@ -307,6 +310,24 @@ internal class ExistingContactOperationsFactory(
}
}

private fun updateRelations(
newContact: MutableContact,
oldContact: Contact
): List<ContentProviderOperation> {
if (newContact.containsColumn(Relations).not()) {
return emptyList()
}
return buildOperations(
forContactId = newContact.contactId,
oldValues = oldContact.relations,
newValues = newContact.relations,
mimeType = RelationColumns.CONTENT_ITEM_TYPE,
) { labeledValue ->
withValue(RelationColumns.NAME, labeledValue.value.name)
.withRelationLabel(labeledValue.label)
}
}

private fun <T : Any> buildOperations(
forContactId: Long,
mimeType: String,
Expand Down
13 changes: 13 additions & 0 deletions library/src/main/java/com/alexstyl/contactstore/Label.kt
Expand Up @@ -33,4 +33,17 @@ sealed class Label {

data class Custom(val label: String) : Label()
object Other : Label()
object RelationBrother : Label()
object RelationChild : Label()
object RelationDomesticPartner : Label()
object RelationFather : Label()
object RelationFriend : Label()
object RelationManager : Label()
object RelationMother : Label()
object RelationParent : Label()
object RelationPartner : Label()
object RelationReferredBy : Label()
object RelationRelative : Label()
object RelationSister : Label()
object RelationSpouse : Label()
}
Expand Up @@ -11,6 +11,7 @@ import com.alexstyl.contactstore.ContactColumn.Note
import com.alexstyl.contactstore.ContactColumn.Organization
import com.alexstyl.contactstore.ContactColumn.Phones
import com.alexstyl.contactstore.ContactColumn.PostalAddresses
import com.alexstyl.contactstore.ContactColumn.Relations
import com.alexstyl.contactstore.ContactColumn.WebAddresses

class MutableContact internal constructor(
Expand Down Expand Up @@ -40,6 +41,7 @@ class MutableContact internal constructor(
groups: MutableList<GroupMembership>,
linkedAccountValues: List<LinkedAccountValue>,
imAddresses: MutableList<LabeledValue<ImAddress>>,
relations: MutableList<LabeledValue<Relation>>,
override val columns: List<ContactColumn>,
) : Contact {

Expand All @@ -61,6 +63,8 @@ class MutableContact internal constructor(

override val groups: MutableList<GroupMembership> by requireColumn(GroupMemberships, groups)

override val relations: MutableList<LabeledValue<Relation>> by requireColumn(Relations, relations)

override var organization: String? by readWriteField(Organization, organization)
override var jobTitle: String? by readWriteField(Organization, jobTitle)
override var firstName: String? by readWriteField(Names, firstName)
Expand All @@ -83,6 +87,7 @@ class MutableContact internal constructor(
events = mutableListOf(),
postalAddresses = mutableListOf(),
webAddresses = mutableListOf(),
relations = mutableListOf(),
note = null,
isStarred = false,
firstName = null,
Expand Down
Expand Up @@ -47,6 +47,10 @@ data class MutableContactBuilder(
val groupMemberships: List<GroupMembership>
get() = _groups.toList()

private val _relations: MutableList<LabeledValue<Relation>> = mutableListOf()
val relations: List<LabeledValue<Relation>>
get() = _relations.toList()

fun phone(
number: String,
label: Label
Expand Down Expand Up @@ -117,4 +121,8 @@ data class MutableContactBuilder(
LabeledValue(ImAddress(raw = address, protocol = protocol), label)
)
}

fun relation(name: String, label: Label) {
_relations.add(LabeledValue(Relation(name = name), label))
}
}

0 comments on commit 0306104

Please sign in to comment.