diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/databases/ContactsDatabase.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/databases/ContactsDatabase.kt index 295a0bd7..98c74343 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/databases/ContactsDatabase.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/databases/ContactsDatabase.kt @@ -7,6 +7,7 @@ import androidx.room.RoomDatabase import androidx.room.TypeConverters import androidx.sqlite.db.SupportSQLiteDatabase import com.simplemobiletools.contacts.pro.helpers.Converters +import com.simplemobiletools.contacts.pro.helpers.FIRST_CONTACT_ID import com.simplemobiletools.contacts.pro.helpers.FIRST_GROUP_ID import com.simplemobiletools.contacts.pro.helpers.getEmptyLocalContact import com.simplemobiletools.contacts.pro.interfaces.ContactsDao @@ -25,8 +26,6 @@ abstract class ContactsDatabase : RoomDatabase() { abstract fun GroupsDao(): GroupsDao companion object { - private const val FIRST_CONTACT_ID = 1000000 - private var db: ContactsDatabase? = null fun getInstance(context: Context): ContactsDatabase { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Activity.kt index 8fddb344..0b4d439d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Activity.kt @@ -171,26 +171,26 @@ fun BaseSimpleActivity.getTempFile(): File? { } fun BaseSimpleActivity.addContactsToGroup(contacts: ArrayList, groupId: Long) { - val publicContacts = contacts.filter { it.source != SMT_PRIVATE } - val privateContacts = contacts.filter { it.source == SMT_PRIVATE } + val publicContacts = contacts.filter { it.source != SMT_PRIVATE }.toMutableList() as ArrayList + val privateContacts = contacts.filter { it.source == SMT_PRIVATE }.toMutableList() as ArrayList if (publicContacts.isNotEmpty()) { - ContactsHelper(this).addContactsToGroup(contacts, groupId) + ContactsHelper(this).addContactsToGroup(publicContacts, groupId) } if (privateContacts.isNotEmpty()) { - dbHelper.addContactsToGroup(contacts, groupId) + LocalContactsHelper(this).addContactsToGroup(privateContacts, groupId) } } fun BaseSimpleActivity.removeContactsFromGroup(contacts: ArrayList, groupId: Long) { - val publicContacts = contacts.filter { it.source != SMT_PRIVATE } - val privateContacts = contacts.filter { it.source == SMT_PRIVATE } + val publicContacts = contacts.filter { it.source != SMT_PRIVATE }.toMutableList() as ArrayList + val privateContacts = contacts.filter { it.source == SMT_PRIVATE }.toMutableList() as ArrayList if (publicContacts.isNotEmpty() && hasContactPermissions()) { - ContactsHelper(this).removeContactsFromGroup(contacts, groupId) + ContactsHelper(this).removeContactsFromGroup(publicContacts, groupId) } if (privateContacts.isNotEmpty()) { - dbHelper.removeContactsFromGroup(contacts, groupId) + LocalContactsHelper(this).removeContactsFromGroup(privateContacts, groupId) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Context.kt index 50ad82d7..38691b30 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Context.kt @@ -16,7 +16,10 @@ import com.simplemobiletools.contacts.pro.R import com.simplemobiletools.contacts.pro.activities.EditContactActivity import com.simplemobiletools.contacts.pro.activities.ViewContactActivity import com.simplemobiletools.contacts.pro.databases.ContactsDatabase -import com.simplemobiletools.contacts.pro.helpers.* +import com.simplemobiletools.contacts.pro.helpers.CONTACT_ID +import com.simplemobiletools.contacts.pro.helpers.Config +import com.simplemobiletools.contacts.pro.helpers.IS_PRIVATE +import com.simplemobiletools.contacts.pro.helpers.SMT_PRIVATE import com.simplemobiletools.contacts.pro.interfaces.ContactsDao import com.simplemobiletools.contacts.pro.interfaces.GroupsDao import com.simplemobiletools.contacts.pro.models.Contact @@ -25,8 +28,6 @@ import java.io.File val Context.config: Config get() = Config.newInstance(applicationContext) -val Context.dbHelper: DBHelper get() = DBHelper.newInstance(applicationContext) - val Context.contactsDB: ContactsDao get() = ContactsDatabase.getInstance(applicationContext).ContactsDao() val Context.groupsDB: GroupsDao get() = ContactsDatabase.getInstance(applicationContext).GroupsDao() diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt index 71166b20..9e7fec57 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt @@ -22,10 +22,11 @@ const val CONTACT_ID = "contact_id" const val SMT_PRIVATE = "smt_private" // used at the contact source of local contacts hidden from other apps const val IS_PRIVATE = "is_private" const val GROUP = "group" -const val FIRST_GROUP_ID = 10000L const val PHONE_NUMBER_PATTERN = "[^0-9#*+]" const val IS_FROM_SIMPLE_CONTACTS = "is_from_simple_contacts" const val ADD_NEW_CONTACT_NUMBER = "add_new_contact_number" +const val FIRST_CONTACT_ID = 1000000 +const val FIRST_GROUP_ID = 10000L // extras used at third party intents const val KEY_PHONE = "phone" diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Converters.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Converters.kt index 599c17f4..63205ad6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Converters.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Converters.kt @@ -7,12 +7,12 @@ import com.simplemobiletools.contacts.pro.models.* class Converters { private val gson = Gson() + private val longType = object : TypeToken>() {}.type private val stringType = object : TypeToken>() {}.type private val numberType = object : TypeToken>() {}.type private val emailType = object : TypeToken>() {}.type private val addressType = object : TypeToken>() {}.type private val eventType = object : TypeToken>() {}.type - private val groupType = object : TypeToken>() {}.type private val imType = object : TypeToken>() {}.type @TypeConverter @@ -21,6 +21,12 @@ class Converters { @TypeConverter fun stringListToJson(list: ArrayList) = gson.toJson(list) + @TypeConverter + fun jsonToLongList(value: String) = gson.fromJson>(value, longType) + + @TypeConverter + fun longListToJson(list: ArrayList) = gson.toJson(list) + @TypeConverter fun jsonToPhoneNumberList(value: String) = gson.fromJson>(value, numberType) @@ -45,12 +51,6 @@ class Converters { @TypeConverter fun eventListToJson(list: ArrayList) = gson.toJson(list) - @TypeConverter - fun jsonToGroupList(value: String) = gson.fromJson>(value, groupType) - - @TypeConverter - fun groupListToJson(list: ArrayList) = gson.toJson(list) - @TypeConverter fun jsonToIMsList(value: String) = gson.fromJson>(value, imType) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/DBHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/DBHelper.kt deleted file mode 100644 index 8925e303..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/DBHelper.kt +++ /dev/null @@ -1,208 +0,0 @@ -package com.simplemobiletools.contacts.pro.helpers - -import android.app.Activity -import android.content.ContentValues -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper -import android.graphics.BitmapFactory -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import com.simplemobiletools.commons.extensions.getBlobValue -import com.simplemobiletools.commons.extensions.getIntValue -import com.simplemobiletools.commons.extensions.getStringValue -import com.simplemobiletools.contacts.pro.extensions.applyRegexFiltering -import com.simplemobiletools.contacts.pro.extensions.config -import com.simplemobiletools.contacts.pro.models.* - -class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { - private val CONTACTS_TABLE_NAME = "contacts" - private val COL_ID = "id" - private val COL_PREFIX = "prefix" - private val COL_FIRST_NAME = "first_name" - private val COL_MIDDLE_NAME = "middle_name" - private val COL_SURNAME = "surname" - private val COL_SUFFIX = "suffix" - private val COL_NICKNAME = "nickname" - private val COL_PHOTO = "photo" - private val COL_PHONE_NUMBERS = "phone_numbers" - private val COL_EMAILS = "emails" - private val COL_EVENTS = "events" - private val COL_STARRED = "starred" - private val COL_ADDRESSES = "addresses" - private val COL_IMS = "ims" - private val COL_NOTES = "notes" - private val COL_COMPANY = "company" - private val COL_JOB_POSITION = "job_position" - private val COL_GROUPS = "groups" - private val COL_WEBSITES = "websites" - - private val GROUPS_TABLE_NAME = "groups" - private val COL_TITLE = "title" - - private val FIRST_CONTACT_ID = 1000000 - - private val mDb = writableDatabase - - companion object { - const val DB_NAME = "contacts.db" - private const val DB_VERSION = 7 - private var dbInstance: DBHelper? = null - var gson = Gson() - - fun newInstance(context: Context): DBHelper { - if (dbInstance == null) - dbInstance = DBHelper(context) - - return dbInstance!! - } - } - - override fun onCreate(db: SQLiteDatabase) { - db.execSQL("CREATE TABLE $CONTACTS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_FIRST_NAME TEXT, $COL_MIDDLE_NAME TEXT, " + - "$COL_SURNAME TEXT, $COL_PHOTO BLOB, $COL_PHONE_NUMBERS TEXT, $COL_EMAILS TEXT, $COL_EVENTS TEXT, $COL_STARRED INTEGER, " + - "$COL_ADDRESSES TEXT, $COL_NOTES TEXT, $COL_GROUPS TEXT, $COL_PREFIX TEXT, $COL_SUFFIX TEXT, $COL_COMPANY TEXT, $COL_JOB_POSITION TEXT," + - "$COL_WEBSITES TEXT, $COL_NICKNAME TEXT, $COL_IMS TEXT)") - - // start autoincrement ID from FIRST_CONTACT_ID to avoid conflicts - db.execSQL("REPLACE INTO sqlite_sequence (name, seq) VALUES ('$CONTACTS_TABLE_NAME', $FIRST_CONTACT_ID)") - - createGroupsTable(db) - } - - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {} - - private fun createGroupsTable(db: SQLiteDatabase) { - db.execSQL("CREATE TABLE $GROUPS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_TITLE TEXT)") - - // start autoincrement ID from FIRST_GROUP_ID to avoid conflicts - db.execSQL("REPLACE INTO sqlite_sequence (name, seq) VALUES ('$GROUPS_TABLE_NAME', $FIRST_GROUP_ID)") - } - - fun addContactsToGroup(contacts: ArrayList, groupId: Long) { - contacts.forEach { - val currentGroupIds = it.groups.map { it.id } as ArrayList - currentGroupIds.add(groupId) - updateContactGroups(it, currentGroupIds) - } - } - - fun removeContactsFromGroup(contacts: ArrayList, groupId: Long) { - contacts.forEach { - val currentGroupIds = it.groups.map { it.id } as ArrayList - currentGroupIds.remove(groupId) - updateContactGroups(it, currentGroupIds) - } - } - - private fun updateContactGroups(contact: Contact, groupIds: ArrayList) { - val contactValues = fillContactGroupValues(groupIds) - val selection = "$COL_ID = ?" - val selectionArgs = arrayOf(contact.id.toString()) - mDb.update(CONTACTS_TABLE_NAME, contactValues, selection, selectionArgs) - } - - private fun fillContactGroupValues(groupIds: ArrayList): ContentValues { - return ContentValues().apply { - put(COL_GROUPS, gson.toJson(groupIds)) - } - } - - fun getContacts(activity: Activity, selection: String? = null, selectionArgs: Array? = null): ArrayList { - val storedGroups = ContactsHelper(activity).getStoredGroupsSync() - val filterDuplicates = activity.config.filterDuplicates - val contacts = ArrayList() - val projection = arrayOf(COL_ID, COL_PREFIX, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_SUFFIX, COL_NICKNAME, COL_PHONE_NUMBERS, - COL_EMAILS, COL_EVENTS, COL_STARRED, COL_PHOTO, COL_ADDRESSES, COL_IMS, COL_NOTES, COL_GROUPS, COL_COMPANY, COL_JOB_POSITION, COL_WEBSITES) - - val phoneNumbersToken = object : TypeToken>() {}.type - val emailsToken = object : TypeToken>() {}.type - val addressesToken = object : TypeToken>() {}.type - val IMsToken = object : TypeToken>() {}.type - val eventsToken = object : TypeToken>() {}.type - val groupIdsToken = object : TypeToken>() {}.type - val websitesToken = object : TypeToken>() {}.type - - val cursor = mDb.query(CONTACTS_TABLE_NAME, projection, selection, selectionArgs, null, null, null) - cursor.use { - while (cursor.moveToNext()) { - val id = cursor.getIntValue(COL_ID) - val prefix = cursor.getStringValue(COL_PREFIX) - val firstName = cursor.getStringValue(COL_FIRST_NAME) - val middleName = cursor.getStringValue(COL_MIDDLE_NAME) - val surname = cursor.getStringValue(COL_SURNAME) - val suffix = cursor.getStringValue(COL_SUFFIX) - val nickname = cursor.getStringValue(COL_NICKNAME) - - val phoneNumbersJson = cursor.getStringValue(COL_PHONE_NUMBERS) - val phoneNumbers = if (phoneNumbersJson == "[]") ArrayList() else gson.fromJson>(phoneNumbersJson, phoneNumbersToken) - ?: ArrayList(1) - - // labels can be null at upgrading from older app versions, when the label wasn't available at all yet - phoneNumbers.filter { it.label == null }.forEach { - it.label = "" - } - - val emailsJson = cursor.getStringValue(COL_EMAILS) - val emails = if (emailsJson == "[]") ArrayList() else gson.fromJson>(emailsJson, emailsToken) - ?: ArrayList(1) - - emails.filter { it.label == null }.forEach { - it.label = "" - } - - val addressesJson = cursor.getStringValue(COL_ADDRESSES) - val addresses = if (addressesJson == "[]") ArrayList() else gson.fromJson>(addressesJson, addressesToken) - ?: ArrayList(1) - - addresses.filter { it.label == null }.forEach { - it.label = "" - } - - val IMsJson = cursor.getStringValue(COL_IMS) - val IMs = if (IMsJson == "[]") ArrayList() else gson.fromJson>(IMsJson, IMsToken) ?: ArrayList(1) - - val eventsJson = cursor.getStringValue(COL_EVENTS) - val events = if (eventsJson == "[]") ArrayList() else gson.fromJson>(eventsJson, eventsToken) - ?: ArrayList(1) - - val photoByteArray = cursor.getBlobValue(COL_PHOTO) ?: null - val photo = if (photoByteArray?.isNotEmpty() == true) { - try { - BitmapFactory.decodeByteArray(photoByteArray, 0, photoByteArray.size) - } catch (e: OutOfMemoryError) { - null - } - } else { - null - } - - val notes = cursor.getStringValue(COL_NOTES) - val starred = cursor.getIntValue(COL_STARRED) - - val groupIdsJson = cursor.getStringValue(COL_GROUPS) - val groupIds = if (groupIdsJson == "[]") ArrayList() else gson.fromJson>(groupIdsJson, groupIdsToken) - ?: ArrayList(1) - val groups = storedGroups.filter { groupIds.contains(it.id) } as ArrayList - - val company = cursor.getStringValue(COL_COMPANY) - val jobPosition = cursor.getStringValue(COL_JOB_POSITION) - val organization = Organization(company, jobPosition) - - val websitesJson = cursor.getStringValue(COL_WEBSITES) - val websites = if (websitesJson == "[]") ArrayList() else gson.fromJson>(websitesJson, websitesToken) - ?: ArrayList(1) - - val cleanPhoneNumbers = ArrayList() - if (filterDuplicates) { - phoneNumbers.mapTo(cleanPhoneNumbers) { PhoneNumber(it.value.applyRegexFiltering(), 0, "") } - } - - val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, "", phoneNumbers, emails, addresses, - events, SMT_PRIVATE, starred, id, "", photo, notes, groups, organization, websites, cleanPhoneNumbers, IMs) - contacts.add(contact) - } - } - return contacts - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/LocalContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/LocalContactsHelper.kt index f9fc990d..d8e52fc3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/LocalContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/LocalContactsHelper.kt @@ -6,10 +6,7 @@ import android.graphics.BitmapFactory import android.net.Uri import android.provider.MediaStore import com.simplemobiletools.contacts.pro.extensions.* -import com.simplemobiletools.contacts.pro.models.Contact -import com.simplemobiletools.contacts.pro.models.LocalContact -import com.simplemobiletools.contacts.pro.models.Organization -import com.simplemobiletools.contacts.pro.models.PhoneNumber +import com.simplemobiletools.contacts.pro.models.* class LocalContactsHelper(val activity: Activity) { fun getAllContacts() = activity.contactsDB.getContacts().map { convertLocalContactToContact(it) }.toMutableList() as ArrayList @@ -21,6 +18,27 @@ class LocalContactsHelper(val activity: Activity) { return activity.contactsDB.insertOrUpdate(localContact) > 0 } + fun addContactsToGroup(contacts: ArrayList, groupId: Long) { + contacts.forEach { + val localContact = convertContactToLocalContact(it) + val newGroups = localContact.groups + newGroups.add(groupId) + newGroups.distinct() + localContact.groups = newGroups + activity.contactsDB.insertOrUpdate(localContact) + } + } + + fun removeContactsFromGroup(contacts: ArrayList, groupId: Long) { + contacts.forEach { + val localContact = convertContactToLocalContact(it) + val newGroups = localContact.groups + newGroups.remove(groupId) + localContact.groups = newGroups + activity.contactsDB.insertOrUpdate(localContact) + } + } + fun deleteContactIds(ids: Array) { ids.forEach { activity.contactsDB.deleteContactId(it) @@ -70,6 +88,8 @@ class LocalContactsHelper(val activity: Activity) { } } + val storedGroups = ContactsHelper(activity).getStoredGroupsSync() + return activity.getEmptyContact().apply { id = localContact.id!! prefix = localContact.prefix @@ -89,7 +109,7 @@ class LocalContactsHelper(val activity: Activity) { thumbnailUri = "" photo = contactPhoto notes = localContact.notes - groups = localContact.groups + groups = storedGroups.filter { localContact.groups.contains(it.id) } as ArrayList organization = Organization(localContact.company, localContact.jobPosition) websites = localContact.websites cleanPhoneNumbers = filteredPhoneNumbers @@ -113,7 +133,7 @@ class LocalContactsHelper(val activity: Activity) { starred = contact.starred addresses = contact.addresses notes = contact.notes - groups = contact.groups + groups = contact.groups.map { it.id }.toMutableList() as ArrayList company = contact.organization.company jobPosition = contact.organization.jobPosition websites = contact.websites diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/LocalContact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/LocalContact.kt index 011fa534..9ff65b9d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/LocalContact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/LocalContact.kt @@ -21,7 +21,7 @@ data class LocalContact( @ColumnInfo(name = "starred") var starred: Int, @ColumnInfo(name = "addresses") var addresses: ArrayList
, @ColumnInfo(name = "notes") var notes: String, - @ColumnInfo(name = "groups") var groups: ArrayList, + @ColumnInfo(name = "groups") var groups: ArrayList, @ColumnInfo(name = "company") var company: String, @ColumnInfo(name = "job_position") var jobPosition: String, @ColumnInfo(name = "websites") var websites: ArrayList,