Skip to content

Commit

Permalink
Sync starred contacts and groups
Browse files Browse the repository at this point in the history
  • Loading branch information
moezbhatti committed Dec 27, 2019
1 parent 28cad9b commit 8f67de7
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 51 deletions.
37 changes: 8 additions & 29 deletions data/src/main/java/com/moez/QKSMS/extensions/CursorExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ package com.moez.QKSMS.extensions

import android.database.Cursor
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.subjects.MaybeSubject

fun Cursor.forEach(closeOnComplete: Boolean = true, method: (Cursor) -> Unit = {}) {
moveToPosition(-1)
Expand All @@ -41,21 +39,6 @@ fun <T> Cursor.map(map: (Cursor) -> T): List<T> {
}
}

fun <T> Cursor.mapWhile(map: (Cursor) -> T, predicate: (T) -> Boolean): ArrayList<T> {
val result = ArrayList<T>()

moveToPosition(-1)
while (moveToNext()) {
val item = map(this)

if (!predicate(item)) break

result.add(item)
}

return result
}

/**
* We're using this simple implementation with .range() because of the
* complexities of dealing with Backpressure with a Cursor. We can't simply
Expand All @@ -72,18 +55,14 @@ fun Cursor.asFlowable(): Flowable<Cursor> {
.doOnComplete { close() }
}

fun Cursor.asMaybe(): Maybe<Cursor> {
val subject = MaybeSubject.create<Cursor>()
/**
* Dumps the contents of the cursor as a CSV string
*/
fun Cursor.dump(): String {
val lines = mutableListOf<String>()

if (moveToFirst()) {
subject.onSuccess(this)
} else {
subject.onError(IndexOutOfBoundsException("The cursor has no items"))
}
lines += columnNames.joinToString(",")
forEach { lines += (0 until columnCount).joinToString(",", transform = ::getString) }

subject.doOnComplete { close() }
return subject
return lines.joinToString("\n")
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2019 Moez Bhatti <[email protected]>
*
* This file is part of QKSMS.
*
* QKSMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QKSMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.moez.QKSMS.mapper

import android.content.Context
import android.database.Cursor
import android.provider.ContactsContract
import com.moez.QKSMS.model.ContactGroup
import javax.inject.Inject

class CursorToContactGroupImpl @Inject constructor(
private val context: Context
) : CursorToContactGroup {

companion object {
private val URI = ContactsContract.Groups.CONTENT_URI
private val PROJECTION = arrayOf(
ContactsContract.Groups._ID,
ContactsContract.Groups.TITLE)
private const val SELECTION = "${ContactsContract.Groups.AUTO_ADD}=0 " +
"AND ${ContactsContract.Groups.DELETED}=0 " +
"AND ${ContactsContract.Groups.FAVORITES}=0"

private const val ID = 0
private const val TITLE = 1
}

override fun map(from: Cursor): ContactGroup {
return ContactGroup(from.getLong(ID), from.getString(TITLE))
}

override fun getContactGroupsCursor(): Cursor? {
return context.contentResolver.query(URI, PROJECTION, SELECTION, null, null)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2019 Moez Bhatti <[email protected]>
*
* This file is part of QKSMS.
*
* QKSMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QKSMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.moez.QKSMS.mapper

import android.content.Context
import android.database.Cursor
import android.provider.ContactsContract
import javax.inject.Inject

class CursorToContactGroupMemberImpl @Inject constructor(
private val context: Context
) : CursorToContactGroupMember {

companion object {
private val URI = ContactsContract.Data.CONTENT_URI
private val PROJECTION = arrayOf(
ContactsContract.Data.LOOKUP_KEY,
ContactsContract.Data.DATA1)

private const val SELECTION = "${ContactsContract.Data.MIMETYPE}=?"
private val SELECTION_ARGS = arrayOf(
ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE)

private const val LOOKUP_KEY = 0
private const val GROUP_ID = 1
}

override fun map(from: Cursor): CursorToContactGroupMember.GroupMember {
return CursorToContactGroupMember.GroupMember(from.getString(LOOKUP_KEY), from.getLong(GROUP_ID))
}

override fun getGroupMembersCursor(): Cursor? {
return context.contentResolver.query(URI, PROJECTION, SELECTION, SELECTION_ARGS, null)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class CursorToContactImpl @Inject constructor(
Phone.TYPE,
Phone.LABEL,
Phone.DISPLAY_NAME,
Phone.STARRED,
Phone.CONTACT_LAST_UPDATED_TIMESTAMP
)

Expand All @@ -47,7 +48,8 @@ class CursorToContactImpl @Inject constructor(
const val COLUMN_TYPE = 2
const val COLUMN_LABEL = 3
const val COLUMN_DISPLAY_NAME = 4
const val CONTACT_LAST_UPDATED = 5
const val COLUMN_STARRED = 5
const val CONTACT_LAST_UPDATED = 6
}

override fun map(from: Cursor) = Contact().apply {
Expand All @@ -58,6 +60,7 @@ class CursorToContactImpl @Inject constructor(
type = Phone.getTypeLabel(context.resources, from.getInt(COLUMN_TYPE),
from.getString(COLUMN_LABEL)).toString()
))
starred = from.getInt(COLUMN_STARRED) != 0
lastUpdate = from.getLong(CONTACT_LAST_UPDATED)
}

Expand All @@ -68,4 +71,4 @@ class CursorToContactImpl @Inject constructor(
}
}

}
}
14 changes: 13 additions & 1 deletion data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import io.realm.Sort
class QkRealmMigration : RealmMigration {

companion object {
const val SCHEMA_VERSION: Long = 8
const val SCHEMA_VERSION: Long = 9
}

override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Expand Down Expand Up @@ -118,6 +118,18 @@ class QkRealmMigration : RealmMigration {
version++
}

if (version == 8L) {
realm.schema.create("ContactGroup")
.addField("id", Long::class.java, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED)
.addField("title", String::class.java, FieldAttribute.REQUIRED)
.addRealmListField("contacts", realm.schema.get("Contact"))

realm.schema.get("Contact")
?.addField("starred", Boolean::class.java, FieldAttribute.REQUIRED)

version++
}

check(version >= newVersion) { "Migration missing from v$oldVersion to v$newVersion" }
}

Expand Down
60 changes: 41 additions & 19 deletions data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ import com.moez.QKSMS.extensions.insertOrUpdate
import com.moez.QKSMS.extensions.map
import com.moez.QKSMS.manager.KeyManager
import com.moez.QKSMS.mapper.CursorToContact
import com.moez.QKSMS.mapper.CursorToContactGroup
import com.moez.QKSMS.mapper.CursorToContactGroupMember
import com.moez.QKSMS.mapper.CursorToConversation
import com.moez.QKSMS.mapper.CursorToMessage
import com.moez.QKSMS.mapper.CursorToRecipient
import com.moez.QKSMS.model.Contact
import com.moez.QKSMS.model.ContactGroup
import com.moez.QKSMS.model.Conversation
import com.moez.QKSMS.model.Message
import com.moez.QKSMS.model.MmsPart
Expand All @@ -53,6 +56,8 @@ class SyncRepositoryImpl @Inject constructor(
private val cursorToMessage: CursorToMessage,
private val cursorToRecipient: CursorToRecipient,
private val cursorToContact: CursorToContact,
private val cursorToContactGroup: CursorToContactGroup,
private val cursorToContactGroupMember: CursorToContactGroupMember,
private val keys: KeyManager,
private val phoneNumberUtils: PhoneNumberUtils,
private val rxPrefs: RxSharedPreferences
Expand Down Expand Up @@ -89,6 +94,7 @@ class SyncRepositoryImpl @Inject constructor(
.toMutableMap()

realm.delete(Contact::class.java)
realm.delete(ContactGroup::class.java)
realm.delete(Conversation::class.java)
realm.delete(Message::class.java)
realm.delete(MmsPart::class.java)
Expand Down Expand Up @@ -234,46 +240,44 @@ class SyncRepositoryImpl @Inject constructor(

realm.executeTransaction {
realm.delete(Contact::class.java)
realm.delete(ContactGroup::class.java)

contacts = realm.copyToRealm(contacts)
realm.insertOrUpdate(getContactGroups(contacts))

// Update all the recipients with the new contacts
val updatedRecipients = recipients.map { recipient ->
recipient.apply {
contact = contacts.firstOrNull {
it.numbers.any { phoneNumberUtils.compare(recipient.address, it.address) }
}
recipients.forEach { recipient ->
recipient.contact = contacts.find { contact ->
contact.numbers.any { phoneNumberUtils.compare(recipient.address, it.address) }
}
}

realm.insertOrUpdate(updatedRecipients)
realm.insertOrUpdate(recipients)
}

}
}

override fun syncContact(address: String): Boolean {
// See if there's a contact that matches this phone number
var contact = getContacts().firstOrNull {
it.numbers.any { number -> phoneNumberUtils.compare(number.address, address) }
var contact = getContacts().find { contact ->
contact.numbers.any { number -> phoneNumberUtils.compare(number.address, address) }
} ?: return false

Realm.getDefaultInstance().use { realm ->
val recipients = realm.where(Recipient::class.java).findAll()
val recipients = realm.where(Recipient::class.java).findAll().filter { recipient ->
contact.numbers.any { number ->
phoneNumberUtils.compare(recipient.address, number.address)
}
}

realm.executeTransaction {
contact = realm.copyToRealmOrUpdate(contact)

// Update all the matching recipients with the new contact
val updatedRecipients = recipients
.filter { recipient ->
contact.numbers.any { number ->
phoneNumberUtils.compare(recipient.address, number.address)
}
}
.map { recipient -> recipient.apply { this.contact = contact } }

realm.insertOrUpdate(updatedRecipients)
recipients.forEach { recipient -> recipient.contact = contact }

realm.insertOrUpdate(recipients)
}
}

Expand All @@ -293,4 +297,22 @@ class SyncRepositoryImpl @Inject constructor(
} ?: listOf()
}

}
private fun getContactGroups(contacts: List<Contact>): List<ContactGroup> {
val groupMembers = cursorToContactGroupMember.getGroupMembersCursor()
?.map(cursorToContactGroupMember::map)
.orEmpty()

val groups = cursorToContactGroup.getContactGroupsCursor()
?.map(cursorToContactGroup::map)
.orEmpty()

groups.forEach { group ->
group.contacts.addAll(groupMembers
.filter { member -> member.groupId == group.id }
.mapNotNull { member -> contacts.find { contact -> contact.lookupKey == member.lookupKey } })
}

return groups
}

}
28 changes: 28 additions & 0 deletions domain/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroup.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2019 Moez Bhatti <[email protected]>
*
* This file is part of QKSMS.
*
* QKSMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QKSMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.moez.QKSMS.mapper

import android.database.Cursor
import com.moez.QKSMS.model.ContactGroup

interface CursorToContactGroup : Mapper<Cursor, ContactGroup> {

fun getContactGroupsCursor(): Cursor?

}
Loading

0 comments on commit 8f67de7

Please sign in to comment.