forked from upstream-mirrors/quik
Sync starred contacts and groups
This commit is contained in:
@@ -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)
|
||||
@@ -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
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
*
|
||||
* 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
*
|
||||
* 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,7 @@ class CursorToContactImpl @Inject constructor(
|
||||
Phone.TYPE,
|
||||
Phone.LABEL,
|
||||
Phone.DISPLAY_NAME,
|
||||
Phone.STARRED,
|
||||
Phone.CONTACT_LAST_UPDATED_TIMESTAMP
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -68,4 +71,4 @@ class CursorToContactImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -234,19 +240,19 @@ 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -254,26 +260,24 @@ class SyncRepositoryImpl @Inject constructor(
|
||||
|
||||
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 } }
|
||||
recipients.forEach { recipient -> recipient.contact = contact }
|
||||
|
||||
realm.insertOrUpdate(updatedRecipients)
|
||||
realm.insertOrUpdate(recipients)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user