Skip to content

Commit

Permalink
Include Groups in TestContactStore
Browse files Browse the repository at this point in the history
  • Loading branch information
alexstyl committed Feb 4, 2022
1 parent 9eb1264 commit 56075d4
Show file tree
Hide file tree
Showing 15 changed files with 526 additions and 161 deletions.
Original file line number Diff line number Diff line change
@@ -1,38 +1,52 @@
package com.alexstyl.contactstore.test

import android.provider.ContactsContract
import com.alexstyl.contactstore.Contact
import com.alexstyl.contactstore.ContactColumn
import com.alexstyl.contactstore.EventDate
import com.alexstyl.contactstore.GroupMembership
import com.alexstyl.contactstore.ImAddress
import com.alexstyl.contactstore.ImageData
import com.alexstyl.contactstore.LabeledValue
import com.alexstyl.contactstore.LinkedAccountValue
import com.alexstyl.contactstore.LookupKey
import com.alexstyl.contactstore.MailAddress
import com.alexstyl.contactstore.Note
import com.alexstyl.contactstore.PhoneNumber
import com.alexstyl.contactstore.PostalAddress
import com.alexstyl.contactstore.Relation
import com.alexstyl.contactstore.SipAddress
import com.alexstyl.contactstore.WebAddress

public data class StoredContact(
val contactId: Long,
val isStarred: Boolean = false,
val prefix: String? = null,
val firstName: String? = null,
val middleName: String? = null,
val lastName: String? = null,
val suffix: String? = null,
val phoneticFirstName: String? = null,
val phoneticMiddleName: String? = null,
val phoneticLastName: String? = null,
val imageData: ImageData? = null,
val organization: String? = null,
val jobTitle: String? = null,
val webAddresses: List<LabeledValue<WebAddress>> = emptyList(),
val phones: List<LabeledValue<PhoneNumber>> = emptyList(),
val mails: List<LabeledValue<MailAddress>> = emptyList(),
val events: List<LabeledValue<EventDate>> = emptyList(),
val postalAddresses: List<LabeledValue<PostalAddress>> = emptyList(),
val note: Note? = null,
val nickname: String? = null,
val fullNameStyle: Int = ContactsContract.FullNameStyle.UNDEFINED,
val phoneticNameStyle: Int = ContactsContract.PhoneticNameStyle.UNDEFINED,
val groups: List<GroupMembership> = emptyList()
)
override val contactId: Long,
override val isStarred: Boolean = false,
override val prefix: String? = null,
override val firstName: String? = null,
override val middleName: String? = null,
override val lastName: String? = null,
override val suffix: String? = null,
override val phoneticFirstName: String? = null,
override val phoneticMiddleName: String? = null,
override val phoneticLastName: String? = null,
override val imageData: ImageData? = null,
override val organization: String? = null,
override val jobTitle: String? = null,
override val webAddresses: List<LabeledValue<WebAddress>> = emptyList(),
override val phones: List<LabeledValue<PhoneNumber>> = emptyList(),
override val mails: List<LabeledValue<MailAddress>> = emptyList(),
override val events: List<LabeledValue<EventDate>> = emptyList(),
override val postalAddresses: List<LabeledValue<PostalAddress>> = emptyList(),
override val note: Note? = null,
override val nickname: String? = null,
override val fullNameStyle: Int = ContactsContract.FullNameStyle.UNDEFINED,
override val phoneticNameStyle: Int = ContactsContract.PhoneticNameStyle.UNDEFINED,
override val groups: List<GroupMembership> = emptyList(),
override val displayName: String? = null,
override val lookupKey: LookupKey? = null,
override val sipAddresses: List<LabeledValue<SipAddress>> = emptyList(),
override val linkedAccountValues: List<LinkedAccountValue> = emptyList(),
override val imAddresses: List<LabeledValue<ImAddress>> = emptyList(),
override val relations: List<LabeledValue<Relation>> = emptyList(),
override val columns: List<ContactColumn> = emptyList()
) : Contact
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.alexstyl.contactstore.test

public data class StoredContactGroup(
val groupId: Long,
val title: String = "",
val note: String? = null,
val isDeleted : Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,26 @@ import android.provider.ContactsContract
import com.alexstyl.contactstore.Contact
import com.alexstyl.contactstore.ContactColumn
import com.alexstyl.contactstore.ContactGroup
import com.alexstyl.contactstore.ContactOperation
import com.alexstyl.contactstore.ContactOperation.Delete
import com.alexstyl.contactstore.ContactOperation.DeleteGroup
import com.alexstyl.contactstore.ContactOperation.Insert
import com.alexstyl.contactstore.ContactOperation.InsertGroup
import com.alexstyl.contactstore.ContactOperation.Update
import com.alexstyl.contactstore.ContactOperation.UpdateGroup
import com.alexstyl.contactstore.ContactPredicate
import com.alexstyl.contactstore.ContactStore
import com.alexstyl.contactstore.DisplayNameStyle
import com.alexstyl.contactstore.ExperimentalContactStoreApi
import com.alexstyl.contactstore.GroupsPredicate
import com.alexstyl.contactstore.ImmutableContactGroup
import com.alexstyl.contactstore.MutableContact
import com.alexstyl.contactstore.MutableContactGroup
import com.alexstyl.contactstore.PartialContact
import com.alexstyl.contactstore.SaveRequest
import com.alexstyl.contactstore.containsColumn
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map

/**
Expand All @@ -31,41 +40,81 @@ import kotlinx.coroutines.flow.map
*/
@ExperimentalContactStoreApi
public class TestContactStore(
contactsSnapshot: List<StoredContact> = emptyList()
contactsSnapshot: List<StoredContact> = emptyList(),
contactGroupsSnapshot: List<StoredContactGroup> = emptyList(),
) : ContactStore {

private val snapshot: MutableStateFlow<List<StoredContact>> = MutableStateFlow(contactsSnapshot)
private val contacts: MutableStateFlow<List<StoredContact>> = MutableStateFlow(contactsSnapshot)
private val contactGroups: MutableStateFlow<List<StoredContactGroup>> =
MutableStateFlow(contactGroupsSnapshot)

override suspend fun execute(request: SaveRequest.() -> Unit) {
val saveRequest = SaveRequest().apply(request)
executeInternal(saveRequest)
}

override suspend fun execute(request: SaveRequest) {
request.requests.forEach { operation ->
when (operation) {
is ContactOperation.Delete -> deleteContact(withId = operation.contactId)
is ContactOperation.Insert -> insertContact(operation.contact)
is ContactOperation.Update -> updateContact(operation.contact)
}
}
executeInternal(request)
}

override suspend fun execute(request: SaveRequest.() -> Unit) {
val saveRequest = SaveRequest().apply(request)
private suspend fun executeInternal(saveRequest: SaveRequest) {
saveRequest.requests.forEach { operation ->
when (operation) {
is ContactOperation.Delete -> deleteContact(withId = operation.contactId)
is ContactOperation.Insert -> insertContact(operation.contact)
is ContactOperation.Update -> updateContact(operation.contact)
is Delete -> deleteContact(withId = operation.contactId)
is Insert -> insertContact(operation.contact)
is Update -> updateContact(operation.contact)
is DeleteGroup -> deleteContactGroup(operation.groupId)
is InsertGroup -> insertGroup(operation.group)
is UpdateGroup -> updateGroup(operation.group)
}
}
}

private suspend fun updateGroup(group: MutableContactGroup) {
val currentGroup = contactGroups.value
.find { it.groupId == group.groupId } ?: return
val updatedGroup = currentGroup.copy(
title = group.title,
note = group.note
)
val newList = contactGroups.value.toMutableList()
.replace(updatedGroup) {
it.groupId == group.groupId
}
.toList()
contactGroups.emit(newList)
}

private suspend fun deleteContact(withId: Long) {
snapshot.emit(
snapshot.value.dropWhile { it.contactId == withId }
contacts.emit(
contacts.value.dropWhile { it.contactId == withId }
)
}

private suspend fun deleteContactGroup(withId: Long) {
contactGroups.emit(
contactGroups.value.dropWhile { it.groupId == withId }
)
}

private suspend fun insertGroup(group: MutableContactGroup) {
val current = contactGroups.value
contactGroups.emit(
current.toMutableList().apply {
add(
StoredContactGroup(
groupId = group.groupId,
title = group.title,
note = group.note
)
)
}
)
}

private suspend fun insertContact(contact: MutableContact) {
val current = snapshot.value
snapshot.emit(
val current = contacts.value
contacts.emit(
current.toMutableList()
.apply {
add(
Expand Down Expand Up @@ -118,7 +167,7 @@ public class TestContactStore(
}

private suspend fun updateContact(contact: MutableContact) {
val currentContact = snapshot.value
val currentContact = contacts.value
.find { it.contactId == contact.contactId } ?: return
val updatedContact = currentContact.copy(
firstName = contact.takeIfContains(ContactColumn.Names) { contact.firstName }
Expand Down Expand Up @@ -156,7 +205,8 @@ public class TestContactStore(
?: currentContact.events,
postalAddresses = contact.takeIfContains(ContactColumn.PostalAddresses) { contact.postalAddresses }
?: currentContact.postalAddresses,
note = contact.takeIfContains(ContactColumn.Note) { contact.note } ?: currentContact.note,
note = contact.takeIfContains(ContactColumn.Note) { contact.note }
?: currentContact.note,
nickname = contact.takeIfContains(ContactColumn.Nickname) { contact.nickname }
?: currentContact.nickname,
groups = contact
Expand All @@ -165,12 +215,12 @@ public class TestContactStore(
fullNameStyle = contact.takeIfContains(ContactColumn.Names) { contact.fullNameStyle }
?: currentContact.fullNameStyle
)
val newList = snapshot.value.toMutableList()
val newList = contacts.value.toMutableList()
.replace(updatedContact) {
it.contactId == contact.contactId
}
.toList()
snapshot.emit(newList)
contacts.emit(newList)
}

private fun <T> List<T>.replace(newValue: T, block: (T) -> Boolean): List<T> {
Expand All @@ -184,16 +234,32 @@ public class TestContactStore(
columnsToFetch: List<ContactColumn>,
displayNameStyle: DisplayNameStyle
): Flow<List<Contact>> {
return snapshot
return contacts
.map { contacts ->
contacts.filter { current ->
matchesPredicate(contact = current, predicate)
}.keepColumns(columnsToFetch)
}
}

override fun fetchContactGroups(): Flow<List<ContactGroup>> {
TODO("Not yet implemented")
override fun fetchContactGroups(predicate: GroupsPredicate?): Flow<List<ContactGroup>> {
return combine(contactGroups.map { groups ->
groups.filter { group ->
matchesPredicate(group, predicate)
}
}, contacts) { groups, contacts ->
groups
.map { group ->
ImmutableContactGroup(
groupId = group.groupId,
title = group.title,
note = group.note,
contactCount = contacts.count { contact ->
contact.groups.any { membership -> membership.groupId == group.groupId }
}
)
}
}
}

private fun matchesPredicate(
Expand All @@ -212,6 +278,26 @@ public class TestContactStore(
}
}

private fun matchesPredicate(
group: StoredContactGroup,
predicate: GroupsPredicate?
): Boolean {
if (predicate == null) return group.isDeleted.not()
return when (predicate) {
is GroupsPredicate.GroupLookup -> {
val passesIdCheck = predicate.inGroupIds == null || predicate.inGroupIds.orEmpty()
.contains(group.groupId)
val passedDeletedCheck = if (predicate.includeDeleted) {
true
} else {
group.isDeleted.not()
}
passesIdCheck && passedDeletedCheck
}
}
}


private fun matchesContact(
predicate: ContactPredicate.ContactLookup,
contact: StoredContact
Expand Down
79 changes: 0 additions & 79 deletions library-test/src/test/java/com/alexstyl/contactstore/Fixtures.kt

This file was deleted.

Loading

0 comments on commit 56075d4

Please sign in to comment.